#!/bin/sh
# Tazpkg - Tiny autonomus zone packages manager.
#
# This is a lightwight packages manager for *.tazpkg files, all written in
# SHell script. It works well with Busybox ash shell and bash. Tazpkg let you
# list, install, remove, download or get information about a package, you can
# use 'tazpkg usage' to get a list of commands with a short description.
#
# (C) 2007 SliTaz - GNU General Public License v3.
# Author : <pankso@slitaz.org>
#
VERSION=1.2

####################
# Script variables #
####################

DEFAULT_MIRROR=http://download.tuxfamily.org/slitaz/packages/cooking/
CATEGORIES="base-system base-apps x-window extra"

# Initialize some variables to use words
# rater than numbers for functions and actions.
COMMAND=$1
PACKAGE=${2%.tazpkg}
TARGET_DIR=$3
TOP_DIR=`pwd`
TMP_DIR=/tmp/tazpkg-$$-$RANDOM

# Path to tazpkg used dir and configuration files
LOCALSTATE=/var/lib/tazpkg
INSTALLED=$LOCALSTATE/installed
CACHE_DIR=/var/cache/tazpkg
MIRROR=$LOCALSTATE/mirror
PACKAGE_LIST=$LOCALSTATE/packages.list

# Check if the directories and files used by Tazpkg
# exists. If not and user is root we creat them.
if test $(id -u) = 0 ; then
	if [ ! -d "$CACHE_DIR" ] ; then
		mkdir -p $CACHE_DIR
	fi
	if [ ! -d "$INSTALLED" ] ; then
	  mkdir -p $INSTALLED
	fi
	if [ ! -f "$LOCALSTATE/mirror" ] ; then
	  echo "$DEFAULT_MIRROR" > $LOCALSTATE/mirror
	fi
fi

####################
# Script functions #
####################

# Print the usage.
usage ()
{
	echo -e "SliTaz packages manager - Version: $VERSION\n
\033[1mUsage: \033[0m tazpkg [command] [package|dir|pattern|list|category] [dir|--option]
\033[1mCommands: \033[0m\n
  usage         Print this short usage.
  list          List installed packages on the system by category or all.
  list-mirror   List all available packages on the mirror.
  info          Print informations about the package.
  desc          Print description of a package (if it exist).
  list-files    List of files installed with the package.
  search        Search for a package by pattern or name.
  install       Install a local (*.tazpkg) package (--forced to force).
  install-list  Install all packages from a list of packages.
  remove        Remove the specified package and all installed files.
  extract       Extract a (*.tazpkg) package into a directory.
  pack          Pack an unpacked or prepared package tree.
  recharge      Recharge your packages.list from the mirror.
  get           Download a package into the current directory.
  get-install   Download and install a package from the mirror.
  clean-cache   Clean all packages downloaded in cache directory.
  setup-mirror  Change the mirror url configuration.\n"
}

# Status function.
status() {
	local CHECK=$?
	echo -en "\033[68G"
	if [ $CHECK = 0 ] ; then
		echo -e "\033[1mOK\033[0m"
	else
		echo -e "\033[1mFailed\033[0m"
	fi
}

# Check if user is root to install, or remove packages.
check_root()
{
	if test $(id -u) != 0 ; then
		echo "You must be root to run `basename $0` with this option."
		echo "Please type 'su' and root password to become super-user."
		exit 0
	fi
}

# Check for a package name on cmdline.
check_for_package_on_cmdline()
{
	if [ -z "$PACKAGE" ] ; then
		echo "You must specify a package name on the command line."
		exit 0
	fi
}

# Check if the package (*.tazpkg) is in current dir.
check_for_package_file()
{
	if [ ! -f "$TOP_DIR/$PACKAGE.tazpkg" ] ; then
		echo "Package : $PACKAGE.tazpkg not found..."
		echo "Unable to find the package in : $TOP_DIR"
		echo "You must be in the package directory to install, extract or pack."
		exit 0
	fi
}

# Check for the receipt of an installed package.
check_for_receipt()
{
	if [ ! -f "$INSTALLED/$PACKAGE/receipt" ] ; then
		echo "Unable to find the receipt : $INSTALLED/$PACKAGE/receipt"
		exit 0
	fi
}

# Check if a package is already installed.
check_for_installed_package()
{
	if [ -d "$INSTALLED/${PACKAGE%-[0-9]*}" ] ; then
		echo "$PACKAGE is already installed."
		exit 0
	fi
}

# Check for packages.list to download and install packages.
check_for_packages_list()
{
	if [ ! -f "$LOCALSTATE/packages.list" ] ; then
		echo "Unable to find : $LOCALSTATE/packages.list"
		echo "You must probably run 'tazpkg recharge' as root."
		exit 0
	fi
}

# Check for a package in packages.list.
# Used by get and get-install.
check_for_package_in_list()
{
	echo "Checking if $PACKAGE exist in list... "
	if [ ! "`grep ^$PACKAGE-[0-9] $LOCALSTATE/packages.list`" == "" ] ; then
		PACKAGE=`grep ^$PACKAGE-[0-9] $LOCALSTATE/packages.list`
		echo "Package exist : $PACKAGE.tazpkg"
		echo -n "Download is going to start..."
		status && sleep 1
	else
		echo "Unable to find : $PACKAGE in the list..."
		exit 0
	fi
}

# Extract a package with cpio and gzip.
extract_package()
{
	echo -n "Extracting $PACKAGE... "
	cpio -id < $PACKAGE.tazpkg && rm -f $PACKAGE.tazpkg
	gzip -d fs.cpio.gz
	echo -n "Extracting the file system... "
	cpio -id < fs.cpio && rm fs.cpio
}

# This function install a package in the rootfs.
install_package()
{
	mkdir -p $TMP_DIR
	echo "----"
	echo -n "Copying $PACKAGE... "
	cp $PACKAGE.tazpkg $TMP_DIR
	status
	cd $TMP_DIR
	extract_package
	# Include temporary receipt to get the right variables.
	. $PWD/receipt
	# Make the installed package data dir to store
	# the receipt and the files list.
	mkdir -p $INSTALLED/$PACKAGE
	cp receipt $INSTALLED/$PACKAGE
	# Include installed receipt.
	. $INSTALLED/$PACKAGE/receipt
	# Copy the list of files and the description if found.
	cp files.list $INSTALLED/$PACKAGE
	if [ -f "description.txt" ] ; then
		cp description.txt $INSTALLED/$PACKAGE
	fi
	if [ `cat $INSTALLED/$PACKAGE/receipt | grep pre_install` ] ; then
		# Execute post install commands.
		pre_install
	fi
	echo -n "Installing $PACKAGE... "
	cp -a fs/* /
	status
	# Remove the temporary random directory.
	echo -n "Removing all tmp files... "
	cd .. && rm -rf $TMP_DIR
	status
	if [ `cat $INSTALLED/$PACKAGE/receipt | grep post_install` ] ; then
		# Execute post install commands.
		post_install
	fi
	cd $TOP_DIR
}

# Check for missing deps listed in a receipt packages.
check_for_deps()
{
	echo "Checking for missing dependencies..."
	echo "----"
	for i in $DEPENDS
	do
		if [ ! -d "$INSTALLED/$i" ] ; then
			MISSING_PACKAGES=$i
			echo "Missing : $MISSING_PACKAGES"
		fi
	done
}

# Install all missing deps. First ask user then install all missing deps
# from local dir, cdrom, media or from the mirror. In case we want to
# install packages from local, we need a packages.list to find the version.
install_deps()
{
	echo "----"
	echo -n "Install all missing dependencies? (y/N) : "; read anser
	if [ "$anser" = "y" ] ; then
		for pkg in $DEPENDS
		do
			if [ ! -d "$INSTALLED/$pkg" ] ; then
				# We can install packages from a local dir by greping
				# the TAZPKG_BASENAME in the local packages.list.
				if [ -f "$TOP_DIR/packages.list" ] ; then
					echo "Checking if $pkg exist in local list... "
					TAZPKG_BASENAME=`grep -e ^$pkg-[0-9] $TOP_DIR/packages.list`
					if [ -f "$TAZPKG_BASENAME.tazpkg" ] ; then
						tazpkg install $TAZPKG_BASENAME.tazpkg
					fi
				# Install deps from the mirror.
				else
					if [ ! -f "$LOCALSTATE/packages.list" ] ; then
						tazpkg recharge
					fi
					tazpkg get-install $pkg
				fi
			fi
		done
	else
		echo "Leaving dependencies for $PACKAGE unsolved."
		echo "The package is installed but will probably not work."
	fi
}

###################
# Tazpkg commands #
###################

case "$COMMAND" in
	list)
		# List all installed packages or a specific category.
		#
		if [ "$2" = "category" ] ; then
			echo -e "\033[1m\nPackages categories :\033[0m $CATEGORIES\n"
			exit 0
		fi
		# Check for an asked category.
		if [ -n "$2" ] ; then
			ASKED_CATEGORY=$2
			echo ""
			echo -e "\033[1mPackages in category :\033[0m $ASKED_CATEGORY"
			echo "----"
			for pkg in `ls -1 $INSTALLED`
			do
				. $INSTALLED/$pkg/receipt
				if [ "$CATEGORY" == "$ASKED_CATEGORY" ] ; then
					echo "$PACKAGE ($VERSION)"
					packages=$(($packages+1))
				fi
			done
			echo "----"
			echo "$packages packages in category $ASKED_CATEGORY."
		else
			# By default list all packages and version.
			echo ""
			echo -e "\033[1mList of installed packages : \033[0m"
			echo "----"
			for pkg in $INSTALLED/*
			do
				. $pkg/receipt
				echo "$PACKAGE ($VERSION)"
				packages=$(($packages+1))
			done
			echo "----"
			echo "$packages packages installed."
		fi
		;;
	list-mirror)
		# List all available packages on the mirror.
		#
		check_for_packages_list
		echo ""
		echo -e "\033[1mList of available packages on the mirror : \033[0m"
		echo "----"
		cat $LOCALSTATE/packages.list
		echo "----"
		pkgs=`cat $LOCALSTATE/packages.list | wc -l`
		echo "$pkgs packages in the list."
		;;
	list-files)
		# List files installed with the package.
		#
		check_for_package_on_cmdline
		check_for_receipt
		echo -e "\033[1mInstalled files with : \033[0m $PACKAGE\n"
		echo "----"
		cat $INSTALLED/$PACKAGE/files.list | sort
		echo "----"
		files=`cat $INSTALLED/$PACKAGE/files.list | wc -l`
		echo "$files files installed."
		;;
	info)
		# Informations about package.
		#
		check_for_package_on_cmdline
		check_for_receipt
		. $INSTALLED/$PACKAGE/receipt
		echo ""
		echo -e "\033[1mTazpkg informations :\033[0m\n
Package    : $PACKAGE
Version    : $VERSION
Category   : $CATEGORY
Short desc : $SHORT_DESC
Maintainer : $MAINTAINER"
		if [ ! "$DEPENDS" = "" ] ; then
			echo -e "Depends    : $DEPENDS"
		fi
		if [ ! "$WANTED" = "" ] ; then
			echo -e "Wanted src : $WANTED"
		fi
		if [ ! "$WEB_SITE" = "" ] ; then
			echo -e "Web site   : $WEB_SITE\n"
		else
			echo ""
		fi
		;;
	desc)
		# Display package description.txt if available.
		if [ -f "$INSTALLED/$PACKAGE/description.txt" ] ; then
			echo ""
			echo -e "\033[1mDescription of : \033[0m $PACKAGE\n"
			echo "----"
			cat $INSTALLED/$PACKAGE/description.txt
			echo "----"
		else
			echo "Sorry, no description available for this package."
		fi
		;;
	search)
		# Search for a package by pattern or name.
		#
		if [ -z "$2" ] ; then
			echo "You must specify a pattern or a package name to search."
			echo "Example : 'tazpkg search paint'."
			exit 0
		fi
		echo ""
		echo -e "\033[1mSearch result for : \033[0m $PACKAGE"
		echo ""
		echo "Installed packages :"
		echo "----"
		ls -1 $INSTALLED | grep $PACKAGE
		echo "----"
		echo "Available packages :"
		echo "----"
		if [ -f "$LOCALSTATE/packages.list" ] ; then
			cat $LOCALSTATE/packages.list | grep $PACKAGE
		else
			echo -e "
No 'packages.list' found to check for mirrored packages. For more results,
please run once 'tazpkg recharge' as root before searching.\n"
		fi
		echo "----"
		;;
	install)
		# Install .tazpkg packages.
		#
		check_root
		check_for_package_on_cmdline
		check_for_package_file
		# Check if forced install.
		if [ "$3" = "--forced" ] ; then
			rm -rf $INSTALLED/$PACKAGE
		else
			check_for_installed_package
		fi
		install_package
		# Resolv package deps.
		check_for_deps
		if [ ! "$MISSING_PACKAGES" = "" ] ; then
			install_deps
		fi
		echo "$PACKAGE ($VERSION) is installed."
		;;
	install-list)
		# Install a set of packages from a list.
		#
		check_root
		if [ -z "$2" ] ; then
			echo "You must change directory (cd) to the packages repository,"
			echo "and specify the list of packages to install."
			echo "Example : tazpkg install-all packages.list"
			exit 0
		fi
		# Check if the packages list exist.
		if [ ! -f "$2" ] ; then
			echo "Unable to find : $2"
			exit 0
		else
			LIST=`cat $2`
		fi
		# Install all packages.
		for pkg in $LIST
		do
			tazpkg install $pkg
		done
		;;
	remove)
		# Remove packages.
		#
		check_root
		check_for_package_on_cmdline
		if [ ! -d "$INSTALLED/$PACKAGE" ] ; then
			echo "$PACKAGE is not installed."
			exit 0
		fi
		echo "Remove $PACKAGE ?"
		echo -n "Please confirm (y/N) : "; read anser
		if [ "$anser" = "y" ] ; then
			echo -n "Removing all stuff installed with $PACKAGE... "
			for file in `cat $INSTALLED/$PACKAGE/files.list`
			do
				rm -f $file
			done
			status
			# Remove package data.
			rm -rf $INSTALLED/$PACKAGE
		else
			echo "Uninstallation of $PACKAGE cancelled."
		fi
		;;
	extract)
		# Extract .tazpkg cpio archive into a directory.
		#
		check_for_package_on_cmdline
		check_for_package_file
		# If any directory destination is found on the cmdline
		# we creat one in the current dir using the package name.
		if [ -n "$TARGET_DIR" ] ; then
			DESTDIR=$TARGET_DIR/$PACKAGE
		else
			DESTDIR=$PACKAGE
		fi
		mkdir -p $DESTDIR
		echo -n "Copying original package..."
		cp $PACKAGE.tazpkg $DESTDIR
		status
		cd $DESTDIR
		extract_package
		echo "$PACKAGE is extracted to : $DESTDIR"
		;;
	pack)
		# Creat SliTaz package archive using cpio and gzip.
		#
		check_for_package_on_cmdline
		cd $PACKAGE
		if [ ! -f "receipt" ]; then
			echo "Receipt is missing. Please read the documentation."
			exit 0
		else
			echo "Starting to pack $PACKAGE..."
			echo "----"
			# Creat files.list with redirecting find outpout.
			echo -n "Creating the list of files..." && cd fs
			find . -type f -print > ../files.list
			find . -type l -print >> ../files.list
			cd .. && sed -i s/'^.'/''/ files.list
			status
			# Build cpio archives.
			echo -n "Compressing the fs... "
			find fs -print | cpio -o -H newc > fs.cpio
			gzip fs.cpio && rm -rf fs
			echo -n "Creating full cpio archive... "
			find . -print | cpio -o -H newc > ../$PACKAGE.tazpkg
			echo -n "Restoring original package tree... "
			gzip -d fs.cpio.gz && cpio -id < fs.cpio
			rm fs.cpio && cd ..
			echo "----"
			echo "Package $PACKAGE build succefully."
		fi
		;;
	recharge)
		# Recharge packages.list from a mirror.
		#
		check_root
		cd $LOCALSTATE
		if [ -f "$LOCALSTATE/packages.list" ] ; then
			mv -f packages.list packages.list.bak
		fi
		wget `cat $MIRROR`/packages.list
		;;
	get)
		# Downlowd a package with wget.
		#
		check_for_package_on_cmdline
		check_for_packages_list
		check_for_package_in_list
		wget `cat $MIRROR`/$PACKAGE.tazpkg
		;;
	get-install)
		# Download and install a package.
		#
		check_root
		check_for_package_on_cmdline
		check_for_packages_list
		check_for_installed_package
		check_for_package_in_list
		cd $CACHE_DIR
		if [ -f "$PACKAGE.tazpkg" ] ; then
			echo "$PACKAGE already in the cache : $CACHE_DIR"
		else
			wget `cat $MIRROR`/$PACKAGE.tazpkg
		fi
		install_package
		check_for_deps
		if [ "$MISSING_PACKAGES" = "" ] ; then
			continue
		else
			install_deps
		fi
		;;
	clean-cache)
		# Remove all downloaded packages.
		#
		check_root
		echo -n "Removing all files in cache dir : $CACHE_DIR... "
		rm -rf $CACHE_DIR/*
		status
		;;
	setup-mirror)
		# Change mirror URL.
		#
		check_root
		# Backup old list.
		if [ -f "$LOCALSTATE/mirror" ] ; then
			mv -f $LOCALSTATE/mirror $LOCALSTATE/mirror.bak
		fi
		echo ""
		echo -e "\033[1mActual mirror : \033[0m $MIRROR"
		echo "
Please enter URL of the new mirror (http or ftp).
You must specify the complet address to the directory
of the packages and packages.list file."
		echo ""
		echo -n "New mirror : "
		read NEW_MIRROR_URL
		if [ "$NEW_MIRROR_URL" = "" ] ; then
			echo "Nothing as been change."
		else
			echo "Setting mirror to : $NEW_MIRROR_URL"
			echo "$NEW_MIRROR_URL" > $LOCALSTATE/mirror
		fi
		;;
	usage|*)
		# Print a short help or give usage for an unknow or empty command.
		#
		usage
		;;
esac

exit 0
