#!/bin/sh
#+++
# RCS $Id: gzip_package.sh,v 2.5 1996/12/21 14:15:52 tom Exp $
# title:	gzip_package
# abstract:	convert NS Installer.app package to use gzipped archive.
# author:	T.R.Hageman <tom@basil.icce.rug.nl>
# created:	September 1994
# modified:	(see RCS Log at end)
# copyleft:
#
#		Copyright (C) 1994--1996  Tom R. Hageman.
#
#	This is free software; you can redistribute it and/or modify
#	it under the terms of the GNU General Public License as published by
#	the Free Software Foundation; either version 2 of the License, or
#	(at your option) any later version.
#
#	This software is distributed in the hope that it will be useful,
#	but WITHOUT ANY WARRANTY; without even the implied warranty of
#	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#	GNU General Public License for more details.
#
#	You should have received a copy of the GNU General Public License
#	along with this software; if not, write to the Free Software
#	Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
#
# description:
#
#---

Revision=; Date=
Clean() { expr "$1" : ':* *\(.*[^ $]\) *$*'; }

Revision=`Clean "$Revision: 2.5 $"`
Date=`Clean "$Date: 1996/12/21 14:15:52 $"`

prog=`basename $0`

USAGE="Usage: $prog [options] package...
  -d --debug	Debug mode (don't remove tmp files.)
  -i --info     Show package's compression status.
  -k --keep	Keep original package around.
  -q --quiet	Shut up completely.
  -s --save=*	How much gzip should save at least (in kbytes, or \`nn%'). 
  -u --unzip	Expand gzip'ed package archive to normal (compressed).
  -v --verbose	Talkative mode.
  -h --help	Show help text.
  -V --version	Show version."

tar=gnutar

verbose=
debug=
keep=
quiet=
unzip=
info=
minsavings="5%"

# Determine default action from program name, a la gzip <-> gunzip.
case "$prog" in #(
gunzip*) unzip=yes
esac

StdErr() { echo "$@" >&2; }

# Handle options.
while true; do
{
    case "$1" in #((((((((((
    -V | --version | --versio | --versi | --vers | --ver | --ve | --v )
	echo $prog Version $Revision of $Date.
	exit 0
	;;
    -d | --debug | --debu | --deb | --de | --d )
	debug=yes
#	set -x
	shift
	;;
    -i | --info | --inf | --in | --i )
	info=yes
	shift
	;;
    -k | --keep | --kee | --ke | --k )
	keep=yes
	shift
	;;
    -q | --quiet | --quie | --qui | --qu | --q )
	quiet=yes
	verbose=
	shift
	;;
    -s=* | --save=* | --sav=* | --sa=* | --s=* )
	minsavings=`expr "$1" : '[^=]*=\([0-9][0-9]*%*\)`
	[ "$minsavings" ] ||
	{
	    Stderr "$prog: value for \`$1' should be number or percentage."
	    exit 2
	}
	shift
	;;
    -s | --save | --sav | --sa | --s )
	minsavings=`expr "$2" : '\([0-9[0-9]*%*\)'`
	[ "$minsavings" ] ||
	{
	    Stderr "$prog: value for \`$1 $2' should be number or percentage."
	    exit 2
	}
	shift
	shift
	;;
    -u | --unzip |--unzi | --unz | --un | --u )
	unzip=yes
	shift
	;;
    -v | --verbose | --verbos | --verbo | --verb )
	verbose=yes
	quiet=
#	set -v
	shift
	;;
    -h | --help | --hel | --he | --h )
    	echo "$USAGE"
	exit 0
	;;
    -- )	# option delimiter.
	shift
	break
	;;
    -* )
	StdErr "$prog: invalid option \`$1'"
	StdErr "$USAGE"
	exit 2
	;;
    "" )
	case "$#" in #(
	0)  StdErr "$USAGE"
	    exit 2
	esac
	# silently ignore empty argument.
	shift
	;;   
    * )
	break
	;;
    esac
}
done

status=0
tmpprefix="##$prog##"

# Error handling.

# Unfortunately `continue' does not seem to work from within a shell function,
# So don't forget to add it explicitly after each occurrence of BailOut.
BailOut() { [ "$1" -gt "$status" ] && status=$1; [ "$debug" ] || eval "$onexit"; onexit=; }

Error() {
    StdErr "$prog:" "$@"
    BailOut 1
}


# Support function to yield free space (in kbytes) available in dir.
DiskFreeSpace() {	# $1: <in-directory>
    #                         Available       Capacity
    df "$1" | sed -n 's|^.* \([0-9][0-9]*\)  *[0-9][0-9]% .*|\1|p'
}

# Support function to yield size of package.
DirUsage() {		# $1 <in-directory>
    du -s -l "$1" | sed 's:^\([0-9][0-9]*\).*:\1:'
}

# Safely remove directory recursively, but only if we own it.
WipeDir() {		# $1: <directory>
   chmod -R u+w "$1" >/dev/null 2>&1 && rm -rf "$1"
}

# Function to replace target package with newly-built one.
ReplaceTarget() {
    if [ "$inplace" ]; then
    {
	# in-place is relatively simple.
	backup="$pkgdir~"
	WipeDir "$backup"
	mv "$pkgdir" "$backup" 2>/dev/null ||
	    { Error "Cannot move original package to \`$backup'"; return; }

	onexit='mv "$backup" "$pkgdir" 2>/dev/null ||
		    Stderr "(original package is now named \`$backup'\'')
		'"$onexit"

	mv "$builddir" "$pkgdir" 2>/dev/null ||
	    { Error "Cannot move new package to \`$pkgdir'"; return; }

	onexit=

	[ "$keep" ] || onexit='WipeDir "$backup"'
    }
    else
    {
	# Not in-place; either because there was not enough space on the disk,
	# or because the directory in which the package resides is not
	# writeable.  Anyway, it's a lot harder.

	# Make backup of package in $HOME.
	suffix=
	while [ "$suffix" != "~~~~~~~~" ]
	do
	    backup="$HOME/$pkgname.pkg~$suffix"
	    WipeDir "$backup"
	    [ ! -d "$backup" -a ! -f "$backup" ] && break
	    suffix="$suffix~"
	done
	mkdir "$backup" 2>/dev/null ||
	    { Error "Cannot create backup package \`$backup'"; return; }
	( cd "$pkgdir" && $tar -cf - . ) 2>/dev/null |
	    ( cd "$backup" && $tar -xpf - ) 2>/dev/null ||
		{ Error "copy to backup package \`$backup' failed."; return; }

	onexit='{   WipeDir "$pkgdir/."
		    ( cd "$backup" && $tar -cf - . ) 2>/dev/null |
			( cd "$pkgdir" && $tar -xpf - ) 2>/dev/null
		} || Stderr "(original package is now in \`$backup'\'')
		'"$onexit"

	WipeDir "$pkgdir/."
	( cd "$builddir" && $tar -cf - . ) 2>/dev/null |
	    ( cd "$pkgdir" && $tar -xpf - ) 2>/dev/null ||
		{ Error "Cannot copy new package to \`$pkgdir'"; return; }

	onexit=
	[ "$keep" ] || onexit='WipeDir "$backup"'
	WipeDir "$builddir"
    }
    fi
}

# Catch signals.
onexit=
trap '[ "$debug" ] || eval "$onexit"; trap 0; exit $status' 0
trap 'StdErr "***signalled***"; status=2; exit $status' 1 2 3 15


for arg in "$@"; do

# Strip off trailing slashes.
pkgdir=`expr "$arg"  : '\(.*[^/]\)/*'`
shift

if [ -d "$pkgdir.pkg" ]; then
{
    pkgdir="$pkgdir.pkg"
}
elif [ ! -d "$pkgdir" ]; then
{
    [ -f "$pkgdir" ] || Error "package \`$pkgdir' does not exist."
    Error "\`$pkgdir' is not a directory."
    continue
}
fi

# More sanity checks.
[ "$info" ] || [ -w "$pkgdir" ] || { Error "package \`$pkgdir' is read-only."; continue; }

# Get the root of the package, sans .pkg extension.
pkgname=`basename "$pkgdir" .pkg`

packagesize=`DirUsage "$pkgdir"`

# Load size definitions in *.sizes into shell variables.
CompressedSize=`sed -n 's/^CompressedSize \(.*\)/\1/p' "$pkgdir/$pkgname.sizes"`

# Check if this is already gzipped.
if [ -f "$pkgdir/$pkgname.tar.gz" -a -h "$pkgdir/$pkgname.tar.Z" ]
then
    realCompressedSize=`sed -n 's/^realCompressedSize \(.*\)/\1/p' "$pkgdir/$pkgname.sizes"`
    expands=`expr $realCompressedSize - $CompressedSize`
    sizeneeded=`expr $packagesize + $expands`
    if [ "$info" ]
    then
	mesg=
	[ "$verbose" ] && mesg=" [${packagesize} kbytes, saves ${expands} (`expr $expands \* 100 / $sizeneeded`%)]"
	echo "$pkgdir:	gzipped$mesg"
	BailOut 0; continue
    else
	[ "$unzip" ] || {
	    [ "$quiet" ] || StdErr "$prog: \`$pkgdir' is already gzipped; unchanged"
	    BailOut 0; continue
	}
    fi
else
    if [ "$info" ]
    then
	if [ -f "$pkgdir/$pkgname.tar.Z" ]
	then
	    mesg=compressed
	    [ "$verbose" ] && mesg="$mesg [${packagesize} kbytes]"
	else
	    mesg=installed
	    [ "$verbose" ] && mesg="$mesg [at "`cat "$pkgdir/$pkgname.location"`"]"
	fi
	echo "$pkgdir:	$mesg"
	BailOut 0; continue
    else
	[ "$unzip" ] && {
	    [ "$quiet" ] || StdErr "$prog: \`$pkgdir' is already gunzipped; unchanged"
	    BailOut 0; continue
	}
    fi
    sizeneeded=$packagesize
fi

# Get the directory in which the package resides.
unset parentdir
[ "${parentdir=`expr \"$pkgdir\" : '\(.*\)/[^/][^/]*'`}" ] || parentdir="."

[ "$quiet" -o "$verbose" ] || echo -n "$pkgdir:	"


# We need a temporary work directory which will contain the updated package.
# See where we can create it.

inplace=
# overestimate size about 10 percent to be on the safe side...
[ `expr $sizeneeded \* 11 / 10` -le `DiskFreeSpace "$parentdir"` ] &&
{
    inplace=yes
    builddir="$parentdir/$tmpprefix$$"
    mkdir "$builddir" 2>/dev/null || inplace=
    # If this fails, this is probably due to parent-dir being write-protected,
    # so build the new package in /tmp instead
}
[ "$inplace" ] ||
{
    builddir="/tmp/$tmpprefix$$"
    mkdir "$builddir" 2>/dev/null ||
	{ Error "Cannot create build-directory for \`$pkgdir'."; continue; }
}

# Cleanup.
onexit="WipeDir \"$builddir\""


# Transfer the contents of the original package, except the archive
( cd "$pkgdir" && $tar -cf - `ls -A | fgrep -v "\
$pkgname.tar.Z
$pkgname.tar.gz"` ) 2>/dev/null |
    ( cd "$builddir" && $tar -xpf - ) 2>/dev/null ||
	{ Error "copy to build-directory of \`$pkgdir' failed."; continue; }

if [ "$unzip" ]
then
    # Handle gzipped -> compressed package 

    # Remove the symbolic .Z link and other junk stuff.
    rm -f $builddir/$pkgname.tar.Z $builddir/funzip

    # Restore original values in *.sizes
    sed 's/^\(CompressedSize\).*/\1 '"$realCompressedSize"'/
	 /^realCompressedSize/d' "$builddir/$pkgname.sizes" >"$builddir/real_sizes"

    # Move the real scripts back.
    for i in pre_install post_install sizes; do
    {
	rm -f "$builddir/$pkgname.$i"
	[ -f "$builddir/real_$i" ] &&
	{
	    mv "$builddir/real_$i" "$builddir/$pkgname.$i" 2>/dev/null ||
		{ Error "Cannot move $i script for \`$pkgdir'."; continue 2; }
	}
    }
    done

    # Now re-expand the contents of the package using compress.
    gunzip < "$pkgdir/$pkgname.tar.gz" | compress > "$builddir/$pkgname.tar.Z" ||
	{ Error "Cannot re-gunzip contents of \`$pkgdir'."; continue; }

    [ "$quiet" ] ||
    {
	[ "$verbose" ] && echo -n "$pkgdir: "
	echo "expands $expands kbytes (`expr $expands \* 100 / $packagesize`%)"
    }

    # Now, finally, replace the target package with the compressed one.
    ReplaceTarget

    BailOut 0; continue
fi

# Handle compressed -> gzipped package

# Now recompress the contents of the package using gzip.
gunzip < "$pkgdir/$pkgname.tar.Z" | gzip -9 > "$builddir/$pkgname.tar.gz" ||
    { Error "Cannot re-gzip contents of \`$pkgdir'."; continue; }

# Copy a tiny ungzipper (funzip [m68k]) to the new package,
# for NS < 3.1 compatibility.
(cd $builddir; sed -n '/^begin .* funzip/,/^end/p' | uudecode) <"$0" ||
    { Error "Cannot add funzip to \`$pkgdir'."; continue; }

# Create a symbolic link to a temporary location for the original contents.
ln -s "/tmp/$tmpprefix$pkgname.tar.Z" "$builddir/$pkgname.tar.Z" ||
    { Error "cannot create symbolic link for \`$pkgname.tar.Z"; continue; }


# Now add a pre_install script to unpack the .gz archive to the temp. location,
# and a post_install script to do the necessary cleanup.

# Move the real scripts out of the way.
for i in pre_install post_install sizes; do
{
    [ -f "$builddir/$pkgname.$i" ] &&
    {
	mv "$builddir/$pkgname.$i" "$builddir/real_$i" 2>/dev/null ||
	    { Error "Cannot move $i script for \`$pkgdir'."; continue 2; }
    }
}
done

# Now create replacement scripts.

SUBSTS='1i\
#!/bin/sh\
# AUTOMATICALLY GENERATED BY '"$prog $Revision of $Date"'.\
\
d="$1"\
n=`basename "$d" .pkg`\
p="$d/$n"\
t="/tmp/'"$tmpprefix"'$n.tar.Z"
'
#NO#/^[	 ]*#/d'
#NO## (Remove comment lines to keep the installed scripts lean.)

sed "$SUBSTS" >"$builddir/$pkgname.pre_install" <<\!!!pre
r="$d/real_pre_install"
E=echo
B='[ -f "$r" ] && exec "$r" "$@"; $E OK.'

# Bail out if there is no gzipped archive. (assume regular package)
[ -f "$p.tar.gz" ] || eval "$B"

# Make sure symlink to work file is in place.
[ -h "$p.tar.Z" ] || {
	# No link. Bail out if looks like regular package after all.
	[ -f "$p.tar.Z" ] && eval "$B"
	# Last-ditch effort: try to create link.
	ln -s "$t" "$p.tar.Z" || { $E FAILED.; exit 1; }
}
# Execute real pre_install script, if it exists, first.
[ -r "$r" ] && { "$r" "$@" || exit 1; } || $E

# Remove work file for good measure.
rm -f "$t"

$E -n "    Gunzipping package ... "
# Try resident gunzip first; if this fails try ungzipper in package.
for u in gunzip "$d/funzip" "$d/gunzip";do
	e=`"$u" <"$p.tar.gz" | compress -f >"$p.tar.Z"`
	[ -z "$e" ] && break
done
[ -z "$e" ] || { $E "FAILED -- $e."; exit 1; }

$E OK.
!!!pre

sed "$SUBSTS" >"$builddir/$pkgname.post_install" <<\!!!post

# Determine working directory used by Installer.
# (Its contents are copied to /NextLibrary/Receipts)
wd=`echo "$0"|sed 's:[^/]*$::;s://*$::'`
case "$wd" in #((
/tmp/*|/private/tmp/*) ;;
*) wd=/tmp
esac
wp="$wd/$n"

# Go cleanup the temporary compressed archive.
rm -f "$t"

size=`sed -n 's/^realCompressedSize \(.*\)/\1/p' "$wp.sizes"`
[ -n "$size" ] && {
	rm -f "$wp.sizes"

	# Restore original values in *.sizes
	sed 's/^\(CompressedSize\).*/\1 '"$size"'/
     		/^realCompressedSize/d' "$p.sizes" >"$wp.sizes"
}

# Replace gzip_package pre_ and post_install with the real scripts.
for i in pre_install post_install
do
	rm -f "$wp.$i"
	[ -f "$d/real_$i" ] && cp -p "$d/real_$i" "$wp.$i"
done

# Now continue with the real post_install script if there is any.
[ -f "$wp.post_install" ] && exec "$wp.post_install" "$@"

echo OK.
!!!post

chmod 555 "$builddir/$pkgname.pre_install" "$builddir/$pkgname.post_install"

# We're finished building the gzipped package.

# Check if it's worth replacing the original package with it.
gzpackagesize=`DirUsage "$builddir"`

case "$minsavings" in #(((
[0-9]% | [1-9][0-9]% )
    i=`expr $minsavings : '\(.*\)%'`
    scaledpackagesize=`expr \( 100 - $i \) \* $packagesize / 100`
    ;;
0 | "" )
    scaledpackagesize=$packagesize
    ;;
* )
    scaledpackagesize=`expr $packagesize - $minsavings`
esac

saves=`expr $packagesize - $gzpackagesize`
case "$saves" in #((
-*)
    expands=`expr "$saves" : "-\(.*\)"`
    mesg="would expand $expands kbytes (`expr $expands \* 100 / $packagesize`%)"
    ;;
*)
    mesg=" $saves kbytes (`expr $saves \* 100 / $packagesize`%)"
    if [ $gzpackagesize -lt $scaledpackagesize ]; then
	mesg="saves $mesg"
    else
	mesg="would save $mesg, if not for --save=$minsavings"
    fi
esac

[ "$quiet" ] ||
{
    [ "$verbose" ] && echo -n "$pkgdir: "
    echo "$mesg"
}
	
[ $gzpackagesize -lt $scaledpackagesize ] ||
    { BailOut 0; continue; }

# Update *.sizes, store original CompressedSize with key `realCompressedSize'
sed 's/^\(CompressedSize\).*/\1 '`expr $CompressedSize - $saves`'/
     $a\
realCompressedSize '$CompressedSize \
	"$builddir/real_sizes" >"$builddir/$pkgname.sizes"
rm -f $builddir/real_sizes

# Now, finally, replace the target package with the gzipped one.
ReplaceTarget

BailOut 0

done

exit $status

#======================================================================
# $Log: gzip_package.sh,v $
# Revision 2.5  1996/12/21 14:15:52  tom
# (WipeDir): new function; use it instead of rm -rf.
# (BailOut): set status from argument first, to avoid collision with WipeDir in onexit.
# (ReplaceTarget): add -p to extracting tar; errors to /dev/null.
# - some comment fixes; remove obsolete debugging comments.
#
# Revision 2.4  1996/11/15 22:19:15  tom
# (diskFreeSpace): yet another NFS-mount fix.
#
# Revision 2.3  1996/02/28 20:29:45  tom
# (SUBSTS): add trailing newline to make it work with GNU sed v?.??
#  [as reported by Gregor Hoffleit <flight@mathi.uni-heidelberg.de>]
#
# Revision 2.2  1995/12/15 14:32:51  tom
# Added -i, --info option and functionality;
# Fixed bug that crashed gzip_package when the package it worked on
# was located on an NFS-mounted volume.
#
# Revision 2.1  1995/07/31 12:14:59  tom
# Fix bug in package expansion message.
#
# Revision 2.0  1995/07/16 12:01:36  tom
# (options): added -h --help; allow multiple package arguments;
# (pre_install): improved error detection, preserve comments;
# (post_install): preserve comments.
#
# Revision 1.7  1995/03/31  15:11:21  tom
# First public release.
#
# Revision 1.6  1995/01/19  23:10:58  tom
# fix bug in funzip uuextraction.
#
# Revision 1.5  1995/01/01  15:07:24  tom
# rename to gzip_package.sh; extract funzip from uuencoded version appended
# to the final gzip_package script.
#
# Revision 1.4  1994/12/30  20:09:39  tom
# fix typos: s/strip gunzip/strip funzip/; s/work_prefix/workprefix/.
#
# Revision 1.3  1994/12/23  14:18:01  tom
# (USAGE): add -u --unzip option, determine its default value from $0;
# (ReplaceTarget): new function, handles final update of target package;
# adjust *.sizes table to reflect new archive size, store original size with
# realCompressedSize key;  NEW: handle uncompression;
# (proto post_install): carefully restore original package state, i.e., replace
# gzip_package generated *_install scripts with the real ones (if any) in the
# work directory, also restore *.sizes table to its original pre-gzip_packaged
# state.
#
# Revision 1.2  1994/09/23  10:09:05  tom
# hammer out the bugs in installer script prototypes.
#
# Revision 1.1  1994/09/23  08:07:43  tom
# Initial revision
#
#======================================================================
