#!/bin/ash
#
# Copyright (C) 2007 Versabanq Innovations Inc.
#
# versatar: a tool for managing archives created from several individual
# tarballs.  The combined file is a .zip archive containing multiple
# tarballs, and can be extracted with a single command.
#
IFS="
"
PATH=$PWD:$PATH

_log()
{
    echo "$@" >&2
}

log()
{
    _log $0: "$@"
}

verbose()
{
    if [ -n "$VERBOSE" ]; then
        _log "$@"
    fi
}

usage()
{
    _log "Usage: $0 <-c|-C|-t|-x|-X> [options...] [directories...]"
    _log "Multi-session tar archive management tool."
    _log
    _log "  Modes:"
    _log "    -c        Create/add to archive"
    _log "    -L        List the files that would be added with -c"
    _log "    -t        Test an archive (show the files we would extract)"
    _log "    -x        Extract an archive's content files"
    _log "    -X        Explode an archive into its component parts"
    _log "  Options:"
    _log "    -?        This help"
    _log "    -v        Verbose output"
    _log "    -C<dir>   Change directory to 'dir' before reading/writing"
    _log "    -s<file>  (-c/-C only) Only add files newer than stamp <file>"
    _log "    -w<file>  Write a new timestamp file for use with -s"
    _log "    -F<zip>   Read/write sessions to container <zipfile>"
    _log "    -f<file>  Read/write session named <file> (inside <zipfile> if given)"
    
    exit 1
}

ziplist()
{
	unzip -qq -l $ZIPFILE "$@" | awk '{print $4}'
}

inredir()
{
    if [ -n "$ZIPFILE" ]; then
        if [ ! -r "$ZIPFILE" ]; then
	    log "File '$ZIPFILE' is not readable!"
	    exit 2
	fi
        if [ -n "$FILE" ]; then
	    unzip -qq -p $ZIPFILE $FILE | "$@" || exit $?
	else
	    # do all the inner tar files, in order
	    unset NEED_WAIT
	    ziplist '*.tar' | while read x; do
		[ -n "$NEED_WAIT" ] && sleep 1
		verbose "-- Reading $ZIPFILE:$x..."
	        unzip -qq -p $ZIPFILE $x | "$@" || exit $?
		if [ -n "$WRITESTAMP" ]; then
		    touch $WRITESTAMP.$x
		    NEED_WAIT=1
		fi
	    done
	fi
    else
        if [ -n "$FILE" ]; then
            if [ ! -r "$FILE" ]; then
	        log "File '$FILE' is not readable!"
		exit 2
            fi
            "$@" <"$FILE" || exit $?
        else
    	    "$@" || exit $?
        fi
    fi
}

outredir()
{
    if [ -n "$ZIPFILE" ]; then
        if [ -z "$FILE" ]; then
	     FILE=$(date +%Y%m%d-%H%M-$$.tar)
	fi
	if [ -e "$FILE" ]; then
	     log "'$FILE' already exists; can't use it as a temp file!"
	     exit 4
	fi
	"$@" >"$FILE" || exit $?
	zip -g $ZIPFILE $FILE
	rm -f $FILE
    else
        if [ -n "$FILE" ]; then
       	     "$@" >"$FILE" || exit $?
        else
            "$@" || exit $?
        fi
    fi
}

anylines()
{
    unset EMPTY
    if ! read x; then
	EMPTY=1
    fi
    
    (
    	[ -n "$EMPTY" ] && echo .
        echo $x
	verbose "$x"
	while read x; do
	    echo $x
	    verbose "$x"
	done
    ) | "$@"
    EX=$?
    if [ -n "$EMPTY" ]; then
    	exit 0
    else
    	exit $EX
    fi
}

createlist()
{
    if [ -z "$*" ]; then
        log "No directory names given!"
	usage
    fi
    if [ -n "$STAMPFILE" ]; then
        if [ ! -e "$STAMPFILE" ]; then
	    log "Stamp file not found: '$STAMPFILE'"
	    exit 2
	fi
        ( cd "$CHDIR" && find $* -xdev -cnewer $STAMPFILE )
    else
    	( cd "$CHDIR" && find $* -xdev )
    fi
}

create()
{
    createlist "$*" | ( cd "$CHDIR" && anylines pax -wdx ustar )
    return $?
}

list()
{
    exec pax
}

extract()
{
    V=""
    [ -n "$VERBOSE" ] && V="v"
    ( cd "$CHDIR" && pax -r${V} )
}

explode()
{
    if [ -z "$ZIPFILE" ]; then
    	log "Must specify -F if you use -X."
    	exit 2
    fi
    if [ ! -r "$ZIPFILE" ]; then
    	log "File '$ZIPFILE' is not readable!"
    	exit 2
    fi
    
    BASENAME=$(basename $ZIPFILE)
    if false; then
	EXP=$BASENAME.explode
        if [ -e "$EXP" ]; then
    	    log "Destination '$EXP' already exists!"
    	    exit 2
        fi
    else
    	EXP=.
    fi
    
    mkdir -p $EXP
    unset V
    if [ -n "$VERBOSE" ]; then
    	V="v"
    fi
    
    ALL=""
    unset ERROR
    ziplist >$EXP/CONTENTS
    while read filename; do
    	ALL="$ALL $filename"
    	case "$filename" in
    	    *.tar)
    	    	verbose "-- Extracting $ZIPFILE:$filename..."
    	    	TDBASE=$(basename "$filename" .tar)
    	    	TD=$EXP/$TDBASE
    	    	mkdir $TD || ERROR=$?
    	    	unzip -qq -p $ZIPFILE $filename >$EXP/$filename || ERROR=$?
    	    	cat $EXP/$filename | ( cd $TD && pax -r${V} )   || ERROR=$?
    		;;
    	    *)
    	    	verbose "-- Reading $ZIPFILE:$filename..."
    	    	unzip -qq -p $ZIPFILE $filename >$EXP/$filename \
    	    		|| ERROR=$?
    		;;
    	esac
    done <$EXP/CONTENTS
    
    cat >$EXP/Makefile <<EOF
ALL=${ALL}

default:
	@echo "Try:"
	@echo "   make world"
	@echo "or one of:"
	@echo "   make $BASENAME"
	@for d in \${ALL}; do \
		echo "   make \$\$d"; \
	done

world: \${ALL} $BASENAME

.PHONY: phony

%.tar: % phony
	../versatar -cf '\$@' -C '\$*' .
		
$BASENAME: phony
	rm -f '\$@'
	zip '\$@' \${ALL}
EOF
    
    if [ -n "$ERROR" ]; then
    	log "Error ($ERROR) delayed from previous activity."
    	exit 2
    fi
    
    exit 0
}

unset COMMAND
unset FILE
unset ZIPFILE
unset STAMPFILE
unset WRITESTAMP
unset VERBOSE
CHDIR="."

while getopts ":?hcLtxXs:w:f:F:C:v" optname; do
    case "$optname" in
    	'?'|h) usage ;;
	c|L|t|x|X) COMMAND=$optname ;;
	s) STAMPFILE="$OPTARG" ;;
	w) WRITESTAMP="$OPTARG" ;;
	f) FILE="$OPTARG" ;;
	F) ZIPFILE="$OPTARG" ;;
	C) CHDIR="$OPTARG" ;;
	v) VERBOSE=1 ;;
    esac
done

shift $(($OPTIND - 1))


# Create the stampfile before doing any work, in case files change while
# we run - at worst, we'll back a file up twice, but not miss it completely.
# FIXME: this would be even better if we used now-1 instead of now, because
# "find -cnewer" only finds files > the stamp, not >= like we want.
if [ -n "$WRITESTAMP" ]; then
    touch .writestamp.$$
fi

case "$COMMAND" in
    L) 
	if [ -n "$FILE" -o -n "$ZIPFILE" ]; then
		log "Don't use -f or -F with -L!"
		exit 1
	fi
	outredir createlist $* ;;
    c) outredir create $* ;;
    t) inredir list $* ;;
    x) inredir extract $* ;;
    X) explode $* ;;
    *) usage ;;
esac

if [ -n "$WRITESTAMP" ]; then
    if [ -e "$WRITESTAMP" ]; then
        mv $WRITESTAMP $WRITESTAMP.bak
    fi
    mv .writestamp.$$ $WRITESTAMP
fi
