#!/bin/sh
# TazPkg - Tiny autonomous zone packages manager, hg.slitaz.org/tazpkg
# get - TazPkg module
# Get package to the cache directory


# 1. "Plain packages" - compiled and packed on cook.slitaz.org using receipts.
#
# Recently added other type of packages: packages that can't be compiled due to the closed sources
# or due to the giant size of that sources. In this case special script: a) downloads package
# compiled for other Linux from official web site, b) converts this package to the TazPkg format,
# c) installs this package as "plain" package.
#
# 2. "Get-packages" - package contains only one special script for downloading/converting.
# Sometimes get-package can get some specified version of the package, sometimes - different
# (latest) version.
#
# Packages 1. and 2. can be found in the wok repository: http://hg.slitaz.org/wok/
#
# 3. "Extra" get-scripts. Next addition - only script, without packaging.
# Extra get-scripts repository: http://hg.slitaz.org/get-scripts/
# Extra get-scripts mirror:     http://mirror.slitaz.org/packages/get/
#
# 4. Converted "extra" packages. Next addition - some packages like LibreOffice / OpenOffice are
# big to compile them like _1. "Plain packages"_ as well as big to convert them on the user side
# (you need a lot of time, CPU, and RAM) and sometimes it is unable on the weak user machines.
# So, some selected and free packages are converted on the SliTaz server side, packaged and
# are ready to download by any user: http://mirror.slitaz.org/packages/extra/
#
#
# Algorithm to get package:
#  - search package in question in the mirrored packages list, download if exists;
#  - search "get-package" in the mirrored packages list, download if exists;
#  - search package in the "extra" get-scripts list, download if exists and run it, get the package.
#
# Note, here doubling. Imagine, you may wanted "palemoon" package.
# 1. No "plain package";
# 2. Existing "get-palemoon" package;
# 3. Existing "palemoon" extra get-script;
# 4. Existing "palemoon" extra converted package.
# User will get 2. He can specify "--extra" option to get 3.
# FIXME: do something with doubling.




# Connect function libraries
. /lib/libtaz.sh
. /usr/lib/slitaz/libpkg.sh

# Get TazPkg working environment
. @@MODULES@@/getenv

. @@MODULES@@/find-depends




# Get cache directory path

get_cache_path() {
	# input:  $1 = DB folder such as "$PKGS_DB" or "$PKGS_DB/undigest/*"
	#         $2 (optional): 'extra' for cache for extra-package scripts
	# output: path to cache directory

	local cache_dir

	if [ "$2" = 'extra' ]; then
		cache_dir="$SAVE_CACHE_DIR/extra/packages"
	elif [ "$1" = "$PKGS_DB" ]; then
		# Main repository
		cache_dir="$SAVE_CACHE_DIR/$SLITAZ_RELEASE/packages"
	else
		# Undigest repository
		cache_dir="$SAVE_CACHE_DIR/${1##*/}/packages"
	fi

	# Make cache directory if absent
	mkdir -p "$cache_dir"
	chmod a+w "$cache_dir"

	echo "$cache_dir"
}


# Download a file from mirror

download_from() {
	# input: "<mirror_url>" "<package_name>-<version>.tazpkg"

	debug "\ndownload_from('$1', '$2')"

	case "$1" in
		# Mirror URL can have a trailing slash or not.
		http://* | https://* | ftp://*)
			debug "  wget -c -T 30 -U $UA ${1%/}/$2"
			q=''; [ -n "$quiet" ] && q='-q'
			# Display abridged wget status (skip info about connections)
			wget -c $q -T 30 -U $UA ${1%/}/$2 2>&1 | awk '$0~"%"{printf "%s\r",$0}' >&2
			;;
		*)
			debug "  cp ${1%/}/$2 ."
			cp ${1%/}/$2 .;;
	esac
}


# This function may be called by a get script.

abort_package() {
	cd "$CUR_DIR"
	rm -rf "$tmp_dir"
	[ -n "$1" ] ||
	set -- 'Could not download "%s" from "%s". Exiting.' "${TARBALL:-$PACKAGE}" "${WGET_URL:-$WEB_SITE}"
	printf "$@"
	exit 1
}


# Get extra-package file to the cache

get_pkg_extra() {
	# input:  $1 extra-package name (like 'dropbox')
	#         $2 (internal): empty or 'redo' (when recursive calling)
	# action: download stuff and make package
	# output: full path to created package

	debug "\nget_pkg_extra('$1', '$2')"

	local mirror extra_cache converted tmp_dir script

	# Check extra.list
	if [ ! -s "$PKGS_DB/extra.list" ]; then
		# Empty extra.list
		if [ "$2" != 'redo' ]; then
			tazpkg recharge >&2
			get_pkg_extra "$1" 'redo'; exit 0
		else
			# Give up
			_ 'File "%s" empty.' 'extra.list' >&2
			die 'Unable to find package "%s" in the extra packages list.' "$(boldify $1)"
		fi
	fi

	# Extra.list exists and not empty
	if ! grep -q "^$(reesc "$1")|" "$PKGS_DB/extra.list"; then
		die 'Unable to find package "%s" in the extra packages list.' "$(boldify $1)"
	fi

	# Extra-package found in extra.list

	if [ -z "$nocache" ]; then
		# Go to cache for "get-install" command; don't move for "get" command
		extra_cache="$(get_cache_path "$PKGS_DB" 'extra')"
		debug "cd '$extra_cache'"
		cd "$extra_cache"

		# Extra-cache should contain packages DB files (packages.info, etc.)
		# to list extra-packages contained in the extra-cache
		[ ! -e 'packages.info' -a ! -e "packages-$SLITAZ_ARCH.info" ] &&
			tazpkg mkdb "$extra_cache" --root='' --forced >/dev/null

		pi="packages-$SLITAZ_ARCH.info"
		[ -e "$pi" ] || pi='packages.info'

		if [ -e "$pi" ]; then
			awk -F$'\t' -vp="$1" '$1==p{exit 1}' $pi
			if [ "$?" -eq 1 ]; then
				[ -z "$quiet" ] && _ 'Package "%s" already in the cache' "$1" >&2
				echo -n "$(pwd)/"
				awk -F$'\t' -vp="$1" '$1==p{printf "%s-%s.tazpkg\n", $1, $2; exit}' $pi
				exit 0
			fi
		fi
	fi

	mirror="$(cat "$PKGS_DB/mirror")"
	debug "mirror='$mirror'"


	# Try converted extra-packages first
	# FIXME: Workaround to get packages filenames (even better to have names and versions separate)
	converted="$(wget -O - http://mirror1.slitaz.org/packages/extra/ 2>/dev/null | \
		tr \'\" $'\n' | grep "$(reesc "$1")-.*\.tazpkg$" | sort -u)"
	debug "converted='$converted'"
	if [ -n "$converted" ]; then
		case "$mirror" in
			http://*|https://*|ftp://*)
				# Default 'http://mirror.slitaz.org/packages/cooking/'
				#      -> 'http://mirror.slitaz.org/packages/extra/'
				debug "wget -T 30 -U '$UA' '${mirror%packages/*}packages/extra/$converted'"
				q=''; [ -n "$quiet" ] && q='-q'
				wget $q -T 30 -U "$UA" "${mirror%packages/*}packages/extra/$converted" \
					2>&1 | awk '$0~"%"{printf "%s\r",$0}' >&2;;
		esac
		if [ -f "$converted" ]; then
			echo "$extra_cache/$converted"; exit 0
		fi
	fi


	# Fails to download converted extra-package; now try to download and execute get-script
	cd ..
	case "$mirror" in
		http://*|https://*|ftp://*)
			# Default 'http://mirror.slitaz.org/packages/cooking/'
			#      -> 'http://mirror.slitaz.org/packages/get/'
			debug "wget -T 30 -U '$UA' '${mirror%packages/*}packages/get/$1'"
			q=''; [ -n "$quiet" ] && q='-q'
			wget $q -T 30 -U "$UA" "${mirror%packages/*}packages/get/$1" \
				2>&1 | awk '$0~"%"{printf "%s\r",$0}' >&2;;
	esac

	if [ ! -f "$1" ]; then
		# TODO: extra package or extra package script? :) Too complicated...
		die 'Unable to download extra package "%s".' "$(boldify $1)"
	fi

	# Extra-package script downloaded in the /var/cache/tazpkg/extra/

	unset_receipt
	PACKAGE="$1"
	script="$(realpath $1)"
	tmp_dir="$(mktemp -d)"; cd "$tmp_dir"
	# Run script
	set -e
	. "$script"
	set +e

	[ "$PWD" != "$tmp_dir" ] && mv $PACKAGE* $tmp_dir
	cd $tmp_dir
	[ -n "$VERSION" ] || VERSION="$(date +%Y%m%d)"
	[ -d "$PACKAGE-$VERSION" ] || mv $PACKAGE $PACKAGE-$VERSION || abort_package
	[ -n "$TARBALL" ] || TARBALL="$(basename $WGET_URL)"

	if [ ! -s "$PACKAGE-$VERSION/receipt" ]; then
		# Create receipt (if script not created it early) using variables from script
		cat > "$PACKAGE-$VERSION/receipt" <<EOT
# SliTaz package receipt.

PACKAGE="$PACKAGE"
VERSION="$VERSION"
CATEGORY="${CATEGORY:-non-free}"
WEB_SITE="${WEB_SITE:-$WEBSITE}"
SHORT_DESC="${SHORT_DESC:-The $PACKAGE software package of SliTaz}"
MAINTAINER="${MAINTAINER:-nobody@slitaz.org}"
LICENSE="${LICENSE:-unknown}"
TARBALL="$TARBALL"
WGET_URL="${WGET_URL:-$WGETURL}"
CONFIG_FILES="$CONFIG_FILES"
SUGGESTED="$SUGGESTED"
PROVIDE="$PROVIDE"
DEPENDS="$DEPENDS"
HOST_ARCH="$HOST_ARCH"
TAGS="$TAGS"
EXTRA_SOURCE_FILES="$EXTRA_SOURCE_FILES"
EOT
	fi

	# Add dependencies which was found to already defined dependencies
	DEPENDS="$(unset DEPENDS; . ./$PACKAGE-$VERSION/receipt; echo $DEPENDS)"
	TMP_DIR="$tmp_dir"
	for i in $(find_depends "$PACKAGE-$VERSION/fs"); do
		case " $DEPENDS " in
			*\ $i\ *) continue;;
		esac
		sed -i "s/^DEPENDS=\"/&$i /" "$PACKAGE-$VERSION/receipt"
	done

	# Remove empty variables
	sed -i '/=""$/d' "$PACKAGE-$VERSION/receipt"

	tazpkg pack "$PACKAGE-$VERSION" gzip >&2

	if [ -z "nocache" ]; then
		# move package to the extra-cache for "get-install" command
		mv -f "$tmp_dir/$PACKAGE-$VERSION.tazpkg" "$extra_cache"
		# Re-make extra packages database
		tazpkg mkdb "$extra_cache" --root='' --forced >/dev/null
	else
		# move package to directory where "tazpkg get" command invoked
		mv -f "$tmp_dir/$PACKAGE-$VERSION.tazpkg" "$CUR_DIR"
	fi

	# Clean
	rm -rf "$tmp_dir"

	# Function output: path to package
	echo "$CUR_DIR/$PACKAGE-$VERSION.tazpkg"
}


# return possible name for a virtual package name

virtual_name() {
	# input:  $1 virtual package name
	#         $2 repository db directory
	local i pi

	pi="$2/packages-$SLITAZ_ARCH.info"
	[ -e "$pi" ] || pi="$2/packages.info"

	unset IFS
	for i in $(grep -hs "^$(reesc "$1")=" "$2/packages.equiv" | sed "s/^$(reesc "$1")=//"); do
		if echo $i | fgrep -q : ; then
			# format 'alternative:newname'
			# if alternative is installed then substitute newname
			if [ -f $INSTALLED/${i%:*}/receipt ]; then
				# substitute package dependency
				echo ${i#*:}
				return
			fi
		elif ! grep -q "^$(reesc "$1")	" "$pi" || [ -f "$INSTALLED/$i/receipt" ]; then
			# unconditional substitution
			echo $i
			return
		fi
	done
	# the real package name
	echo $1
}

virtual_pkg() {
	# input:  $1 virtual package name
	#         $2 repository db directory
	# output: display possible package name
	local pi

	debug "\nvirtual_pkg('$1', '$2')"

	pi="$2/packages-$SLITAZ_ARCH.info"
	[ -e "$pi" ] || pi="$2/packages.info"

	if [ "$tazpkg_command" != 'get-install' ]; then
		# 'get' command: download any package
		if [ -z "$(awk -F$'\t' -vp="$1" '{if ($1 == p) print p}' "$pi")" ]; then
			# This package does not exist in the list, it may be a virtual package
			virtual_name "$1" "$2"
		else
			echo $1
		fi
		return
	fi

	virtual_name "$1" "$2"
}


# Download package file to the cache

get_pkg() {
	# input:  $1 package name either "name" or "name-version"
	#         $2 (internal): empty or 'redo' (when recursive calling)
	# action: download package from mirror to the cache directory
	# output: full path to the downloaded package

	debug "\nget_pkg('$1', '$2')"
	local repo line namever pkgsum pkgfile pi

	IFS=$'\n'
	for rep in $PRIORITY; do
		pi="$rep/packages-$SLITAZ_ARCH.info"
		[ -e "$pi" ] || pi="$rep/packages.info"
		[ -e "$pi" ] || continue

		# If found, output string "<name>-<ver>-<arch>.tazpkg"
		# Field #11 may be:
		#  - empty (old format): treat as arch-dependent, use $SLITAZ_ARCH
		#  - 0: arch-independent (*-any.tazpkg)
		#  - 3: 32-bit \ just use $SLITAZ_ARCH in both cases -
		#  - 6: 64-bit / there's no 32-bit package in 64-bit repo
		pkgfile=$(awk -F$'\t' -vpkg="$(virtual_pkg "$1" "$rep")" -varch="$SLITAZ_ARCH" '
			$1==pkg || $1"-"$2==pkg {
				if (NF >= 11) {
					a = ($11 == "0") ? "any" : arch;
					printf("%s-%s-%s.tazpkg", $1, $2, a);
				} else {
					# Legacy packages.info (no arch column)
					printf("%s-%s.tazpkg", $1, $2);
				}
				exit
			}' "$pi")

		if [ -n "$pkgfile" ]; then
			pkgsum=$(awk -vfile="$pkgfile" '{if($2==file)print $1}' $rep/packages.md5)
			break
		fi
	done
	unset IFS

	debug "  rep='$rep'\n  pkgfile='$pkgfile'\n  pkgsum='$pkgsum'"

	if [ -z "$pkgfile" ]; then
		_ 'Unable to find package "%s" in the mirrored packages list.' "$1" >&2
		# Retry with "get-package"; prevent looping with 'redo'
		if [ "$2" != 'redo' ]; then
			get_pkg "get-$1" 'redo'; exit 0
		else
			# Retry with extra-package
			get_pkg_extra "${1#get-}"
			exit 0
		fi
	fi

	# We need package "$pkgfile" from "$rep" with "$pkgsum"

	if [ -z "$nocache" ]; then
		# Go to cache for "get-install" command; don't move for "get" command
		debug "cd '$(get_cache_path "$rep")'"
		cd "$(get_cache_path "$rep")"
	fi

	# Check if package already downloaded
	if [ -f "$pkgfile" ]; then
		[ -z "$nocache" -a -z "$quiet" ] && _ 'Package "%s" already in the cache' "$pkgfile" >&2

		# Check if downloading complete, resume if not complete
		if ! tail -c 2k "$pkgfile" | fgrep -q '00000000TRAILER'; then
			[ -z "$quiet" ] && _ 'Continuing package "%s" download' "$pkgfile" >&2
			download_from "$(cat "$rep/mirror")" "$pkgfile"
		fi
	else
		# Package absent in the cache, "cold" downloading
		download_from "$(cat "$rep/mirror")" "$pkgfile"
	fi

	# Make sure we downloaded what we want with checksum

	if [ "$($CHECKSUM "$pkgfile" | cut -d' ' -f1)" != "$pkgsum" ]; then
		_ 'Checksum error for "%s"' "$pkgfile" >&2
		rm "$pkgfile"

		# Recharge DBs and try again; prevent looping with 'redo'
		if [ "$2" != 'redo' ]; then
			tazpkg recharge >&2
			get_pkg "$1" 'redo'; exit 0
		else
			# Give up
			# TODO: try another mirror?
			die 'Please wait until the mirror synchronization is complete and try again.'
		fi
	fi

	# Output: path to downloaded package
	printf '%s/%s\n' "$(pwd)" "$pkgfile"
}


# Get local package from /home/slitaz/packages to co-work with the cookutils

get_pkg_cookmode() {
	# input:  $1 package name (like 'advancecomp')
	# action: find package in /home/slitaz/packages (or where defined in the cook.conf)
	# output: full path to the found package
	# ROOT NOT USED

	local PKGS='/home/slitaz/packages' found='0' i
	[ -e "/etc/slitaz/cook.conf" ] && . /etc/slitaz/cook.conf

	# Find local package
	pi="$PKGS/packages-$SLITAZ_ARCH.info"
	[ -e "$pi" ] || pi="$PKGS/packages.info"

	pkgfile="$(awk -F$'\t' -vpkg="$1" -varch="$SLITAZ_ARCH" '{
		if ($1 == pkg) {
			if (NF >= 11) {
				a = ($11 == "0") ? "any" : arch;
				printf("%s-%s-%s.tazpkg", $1, $2, a);
			} else {
				printf("%s-%s.tazpkg", $1, $2);
			}
			exit;
		}
	}' $pi)"

	# Find local provided package
	[ -n "$pkgfile" ] ||
	pkgfile="$(awk -F$'\t' -vpkg="$1" -varch="$SLITAZ_ARCH" '{
		if (index(" " $10 " ", " " pkg " ")) {
			if (NF >= 11) {
				a = ($11 == "0") ? "any" : arch;
				printf("%s-%s-%s.tazpkg", $1, $2, a);
			} else {
				printf("%s-%s.tazpkg", $1, $2);
			}
			exit;
		}
	}' $pi)"

	# packages.info is maintained incrementally by cook (one entry per build),
	# so a .tazpkg seeded/rsynced/bundled into $PKGS without a local cook is
	# NOT indexed -> the awk above yields nothing and the dep silently vanishes
	# under TAZPKG_OFFLINE. Fall back to a direct on-disk glob so cookmode
	# resolution does not hinge on a fresh DB. Digit-anchored version keeps
	# "nettle" from matching "nettle-dev".
	if [ -z "$pkgfile" ] || [ ! -e "$PKGS/$pkgfile" ]; then
		pkgfile=$(ls "$PKGS/$1"-[0-9]*.tazpkg 2>/dev/null | head -n1)
		[ -n "$pkgfile" ] && echo "$pkgfile" && return 0
	fi

	if [ -n "$pkgfile" ] && [ -e "$PKGS/$pkgfile" ]; then
		echo "$PKGS/$pkgfile"
	else
		# Proceed to get package as usual (non-local) -- unless TAZPKG_OFFLINE
		# is set (cook's local bdep install): then stay local, no mirror probe.
		[ -n "$TAZPKG_OFFLINE" ] && return 1 || get_pkg "$1"
	fi
}




# Command 'get-install' calls 'get', then 'install' modules. Check package presence here, on the
# first stage, if '--forced' option not given
if [ "$tazpkg_command" = 'get-install' ]; then
	if grep -qs "^$(reesc "$1")$" "$BLOCKED"; then
		_ 'Package "%s" blocked.' "$1" >&2
		exit 1
	fi

	if [ -z "$forced" ]; then
		awk -F$'\t' -vpv="$1" '$1==pv { exit 1 }' "$PKGS_DB/installed.info"
		if [ "$?" -eq 1 ]; then
			[ -z "$quiet" ] && (
				newline
				_ '"%s" package is already installed.' "$(colorize 34 "$1")"
				longline "$(_ 'You can use the --forced option to force installation.')"
				newline
			) >&2
			# Prevent execution 'install' stage:
			exit 1
		fi
	fi
fi

if [ -n "$cookmode" ]; then
	# When '--cookmode' option given, try to find package in the local cook repository,
	# then, if fails, try to get the package as usual
	get_pkg_cookmode "$1"
elif [ -n "$extra" ]; then
	# When '--extra' option given, extra-package has priority over 'regular' packages
	get_pkg_extra "$1"
else
	# Try to get 'package', then 'get-package', then extra 'package'
	get_pkg "$1"
fi
