#!/usr/bin/env bash
# Copyright 1999-2022 Gentoo Authors
# Distributed under the terms of the GNU General Public License v2

source "${PORTAGE_BIN_PATH}"/isolated-functions.sh || exit 1

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

while [[ $# -gt 0 ]] ; do
	case $1 in
	--ignore)
		shift

		skip_dirs=()
		> "${T}/.ecompress_skip_files" || die
		for skip; do
			if [[ -d ${ED%/}/${skip#/} ]]; then
				skip_dirs+=( "${ED%/}/${skip#/}" )
			else
				rm -f "${ED%/}/${skip#/}.ecompress" || die
				printf -- '%s\n' "${EPREFIX}/${skip#/}" >> "${T}/.ecompress_skip_files" || die
			fi
		done

		if [[ ${#skip_dirs[@]} -gt 0 ]]; then
			while read -r -d '' skip; do
				skip=${skip%.ecompress}
				printf -- '%s\n' "${skip#${D%/}}" >> "${T}/.ecompress_skip_files" || die
			done < <(find "${skip_dirs[@]}" -name '*.ecompress' -print0 -delete || die)
		fi

		if [[ -s ${T}/.ecompress_skip_files && -s ${T}/.ecompress_had_precompressed ]]; then
			# Filter skipped files from ${T}/.ecompress_had_precompressed,
			# using temporary files since these lists can be extremely large.
			LC_COLLATE=C sort -u "${T}/.ecompress_skip_files" > "${T}/.ecompress_skip_files_sorted" || die
			LC_COLLATE=C sort -u "${T}/.ecompress_had_precompressed" > "${T}/.ecompress_had_precompressed_sorted" || die
			LC_COLLATE=C comm -13 "${T}/.ecompress_skip_files_sorted" "${T}/.ecompress_had_precompressed_sorted" > "${T}/.ecompress_had_precompressed" || die
			rm -f "${T}/.ecompress_had_precompressed_sorted" "${T}/.ecompress_skip_files"{,_sorted}
		fi

		exit 0
		;;
	--queue)
		shift

		find_args=()
		for path; do
			if [[ -e ${ED%/}/${path#/} ]]; then
				find_args+=( "${ED%/}/${path#/}" )
			fi
		done

		if [[ ${#find_args[@]} -gt 0 ]]; then
			find_args+=( -type f )
			[[ -n ${PORTAGE_DOCOMPRESS_SIZE_LIMIT} ]] &&
				find_args+=( -size "+${PORTAGE_DOCOMPRESS_SIZE_LIMIT}c" )

			declare -A collisions
			while IFS= read -d '' -r path; do
				# detect the horrible posibility of the ebuild installing
				# colliding compressed and/or uncompressed variants
				# and fail hard (bug #667072)
				#
				# note: to save time, we need to do this only if there's
				# at least one compressed file
				case ${path} in
					*.Z|*.gz|*.bz2|*.lzma|.lz|.lzo|.lz4|*.xz|*.zst)
						vpath=${path%.*}
						for comp in '' .Z .gz .bz2 .lzma .lz .lzo .lz4 .xz .zst; do
							if [[ ${vpath}${comp} != ${path} && \
									-e ${vpath}${comp} ]]; then
								collisions[${path}]=1
								collisions[${vpath}]=1
								# ignore compressed variants in that case
								continue 2
							fi
						done
						printf -- '%s\n' "${path#${D%/}}" >> "${T}"/.ecompress_had_precompressed || die
						;;
				esac

				>> "${path}.ecompress" || die
			done < <(find "${find_args[@]}" -print0 || die)

			if [[ ${#collisions[@]} -gt 0 ]]; then
				eqawarn "QA Notice: Colliding files found by ecompress:"
				eqawarn
				for x in "${!collisions[@]}"; do
					eqawarn "  ${x}"
				done
				eqawarn
				eqawarn "Please remove the extraneous compressed variants."
			fi
		fi

		exit 0
		;;
	--dequeue)
		[[ -n ${2} ]] && die "${0##*/}: --dequeue takes no additional arguments"
		break
		;;
	*)
		die "${0##*/}: unknown arguments '$*'"
		exit 1
		;;
	esac
	shift
done

# setup compression stuff
PORTAGE_COMPRESS=${PORTAGE_COMPRESS-bzip2}
if [[ -z ${PORTAGE_COMPRESS} ]]; then
	find "${ED}" -name '*.ecompress' -delete
	exit 0
fi

if [[ ${PORTAGE_COMPRESS_FLAGS+set} != "set" ]] ; then
	case ${PORTAGE_COMPRESS} in
		bzip2|gzip)  PORTAGE_COMPRESS_FLAGS="-9";;
		# Without setting '-m' lz4 will not compress multiple files at once.
		# See: https://bugs.gentoo.org/672916
		# Setting '--rm' will remove the source files after a successful compression.
		lz4)  PORTAGE_COMPRESS_FLAGS="-m --rm";;
		xz)   PORTAGE_COMPRESS_FLAGS="-q -T$(___makeopts_jobs) --memlimit-compress=50%";;
		zstd) PORTAGE_COMPRESS_FLAGS="-q --rm -T$(___makeopts_jobs)";;
	esac
fi

guess_suffix() {
	set -e

	tmpdir="${T}"/.ecompress$$.${RANDOM}
	mkdir "${tmpdir}"
	cd "${tmpdir}"

	# We have to fill the file enough so that there is something
	# to compress as some programs will refuse to do compression
	# if it cannot actually compress the file
	echo {0..1000} > compressme
	${PORTAGE_COMPRESS} ${PORTAGE_COMPRESS_FLAGS} compressme > /dev/null

	# If PORTAGE_COMPRESS_FLAGS contains -k then we need to avoid
	# having our glob match the uncompressed file here.
	suffix=$(echo compressme.*)
	[[ -z ${suffix} || "${suffix}" == "compressme.*" ]] && \
		suffix=$(echo compressme*)
	suffix=${suffix#compressme}

	cd /
	rm -rf "${tmpdir}"
	echo "${suffix}"
}

# figure out the new suffix
export PORTAGE_COMPRESS_SUFFIX=$(guess_suffix) || die

fix_symlinks() {
	# 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}")
		newdest=${olddest}${PORTAGE_COMPRESS_SUFFIX}
		if [[ "${newdest}" == /* ]] ; then
			[[ -f "${D%/}${newdest}" ]] || continue
		else
			[[ -f "${brokenlink%/*}/${newdest}" ]] || continue
		fi

		something_changed=${brokenlink}
		rm -f "${brokenlink}" &&
		ln -snf "${newdest}" "${brokenlink}${PORTAGE_COMPRESS_SUFFIX}"
		((ret|=$?))
	done < <(find "${ED}" -type l -print0 || die)

	[[ -n ${something_changed} ]] || break
	(( indirection++ ))

	if (( indirection >= 100 )) ; then
		# Protect against possibility of a bug triggering an endless loop.
		eerror "ecompress: too many levels of indirection for" \
			"'${something_changed#${ED%/}}'"
		break
	fi
	done

	return ${ret}
}

export PORTAGE_COMPRESS PORTAGE_COMPRESS_FLAGS
find "${ED}" -name '*.ecompress' -delete -print0 |
	___parallel_xargs -0 "${PORTAGE_BIN_PATH}"/ecompress-file
ret=${?}

if [[ -s ${T}/.ecompress_had_precompressed ]]; then
	eqawarn "QA Notice: One or more compressed files were found in docompress-ed"
	eqawarn "directories. Please fix the ebuild not to install compressed files"
	eqawarn "(manpages, documentation) when automatic compression is used:"
	eqawarn
	n=0
	while read -r f; do
		eqawarn "  ${f}"
		if [[ $(( n++ )) -eq 10 ]]; then
			eqawarn "  ..."
			break
		fi
	done <"${T}"/.ecompress_had_precompressed
fi

fix_symlinks
: $(( ret |= ${?} ))
[[ ${ret} -ne 0 ]] && __helpers_die "${0##*/} failed"

exit ${ret}
