#!@BASH@ # Wrap zpaq as a more generic compression utility # # Copyright 2017 Robert Krawitz (rlk@alum.mit.edu) # # This program 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 program 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 program. If not, see . declare -r ZPAQ=@ZPAQ@ declare METHOD=5 declare VERBOSE= declare ZPCAT= declare DECOMPRESS= declare FORCE= declare KEEP= declare SAVE_META= declare RECURSE= declare STDIN= declare STATUS=0 declare -r PROGNAME=$0 declare -r BASEDIR=$(pwd) declare -r RM=rm declare TMP_FILE= declare TMP_DIR= declare TMP_ARCHIVE= usage() { declare -l USTATUS=${1:-0} echo "Usage: $PROGNAME [OPTIONS] [FILE]..." echo "Compress or decompress FILEs (by default, compress FILEs in place)" echo echo " -c Write to standard output, keep original files unchanged" echo " -d Decompress" echo " -f Force overwrite of output file" echo " -h Print this help" echo " -k Keep (don't delete) input files" echo " -n Do not preserve original filename and timestamp" echo " -N Preserve original filename and timestamp" echo " -q Suppress all messages" echo " -r Operate recursively on directories" echo " -v Print verbose messages" echo " -1 Use minimum compression" echo " -5 Use maximum compression" echo echo "With no FILE, or when only FILE is -, read standard input" exit $USTATUS } while getopts "qvcdhfknNz0123456789" opt ; do case "$opt" in q) VERBOSE= ;; v) VERBOSE=1 ;; c) ZPCAT=1 ;; d) DECOMPRESS=1 ;; f) FORCE=1 ;; h) usage ;; k) KEEP=1 ;; n) SAVE_META=-noattributes ;; N) SAVE_META= ;; r) RECURSE=1 ;; z) DECOMPRESS= ;; [0-9]) METHOD=$opt ;; *) usage 1 ;; esac done shift $(($OPTIND-1)) if [[ -n $RECURSE && -n $ZPCAT ]] ; then echo "May not combine recursive and output to stdout" usage $0 1 fi if [[ -n $RECURSE && -z $* ]] ; then echo "May not use recursive with no inputs" usage $0 1 fi declare -a FILES g() { if [[ $1 == /* ]] ; then echo "$1" else echo "$BASEDIR/$1" fi } build_file_list() { for f in "$@" ; do if [[ -d $f ]] ; then if [[ -n $RECURSE ]] ; then OIFS=$IFS IFS= if [[ -n $DECOMPRESS ]] ; then for f in $(find "$f" -type f -name '*.zpaq' -print) ; do FILES+=($(g "$f")) done else for f in $(find "$f" -type f \! -name '*.zpaq' -print) ; do FILES+=($(g "$f")) done fi IFS=$OIFS else echo "$f: is a directory, skipping" 1>&2 STATUS=1 fi elif [[ -f $f ]] ; then if [[ -n $DECOMPRESS && $f != *.zpaq ]] ; then echo "$f: not a zpaq archive, skipping" 1>&2 STATUS=1 elif [[ -z $DECOMPRESS && $f == *.zpaq ]] ; then echo "$f: is a zpaq archive, skipping" 1>&2 STATUS=1 else FILES+=($(g "$f")) fi else echo "$f: not a plain file, skipping" 1>&2 STATUS=1 fi done } if [[ -z $* || $* == '-' ]] ; then STDIN=1 ZPCAT=1 else build_file_list "$@" fi if [[ -n $ZPCAT && ${#FILES[@]} > 1 ]] ; then echo "May not compress/decompress more than one file to stdout" 1>&2 usage 1 fi # Wrappers to simplify debugging run_zpaq() { if [[ -n $VERBOSE ]] ; then "$ZPAQ" "$@" else "$ZPAQ" "$@" >/dev/null 2>&1 fi } RM() { for f in "$@" ; do if [[ $1 != $TMP_FILE && $1 != $TMP_DIR || $1 != $TMP_ARCHIVE ]] ; then rm -rf $f fi done } do_decompress() { FILE="$1" DEST="$2" [[ -n $3 && -d $3 ]] && cd $3 run_zpaq extract "$FILE" $SAVE_META [[ $? > 0 ]] && STATUS=1 if [[ $(wc -l <<< "$(find . -type f -print)") > 1 ]] ; then echo "Multiple file archive $TMP_ARCHIVE, skipping" 1>&2 exit 1 fi if [[ -z $DEST ]] ; then cat "$(find . -type f -print)" else mv "$(find . -type f -print)" "$DEST" fi } decompress_file() { FILE="$1" DEST="$2" if [[ -d $TMP_DIR ]] ; then # Decompress into a temporary directory. We don't actually # know what's in the archive and don't want to dump arbitrary # contents into the user's directory. We do this in a subshell # so that we don't have to try to cd back to the current directory # (which may be problematic if the directory is remote and we # spend a lot of time in the temp directory). (do_decompress "$FILE" "$DEST" "$TMP_DIR") if [[ $? > 0 ]] ; then STATUS=1 elif [[ -z $ZPCAT && ! -n $KEEP ]] ; then RM "$FILE" fi fi } decompress() { # Decompress: decompress all .zpaq files. if [[ -z ${FILES[*]} ]] ; then # Decompress stdin implies output to stdout ZPCAT=1 cat > "$TMP_ARCHIVE" [[ $? == 0 ]] && decompress_file "$TMP_ARCHIVE" else for f in ${FILES[@]} ; do declare DEST= [[ -z $ZPCAT ]] && DEST="${f%.zpaq}" if [[ -n $DEST && -e $DEST && -z $FORCE ]] ; then echo "${DEST#${BASEDIR}/} exists, skipping" 1>&2 STATUS=1 else decompress_file "$f" "$DEST" fi done fi } do_compress() { FILE="$1" DEST=$(g "$2") cd "${FILE%/*}" run_zpaq add "$DEST" "${FILE##*/}" $SAVE_META -method "$METHOD" } compress_file() { FILE="$1" DEST=${2:-${FILE}.zpaq} # Make sure that DEST really is empty. RM "$DEST" (do_compress "$FILE" "$DEST") if [[ $? > 0 ]] ; then return 1 elif [[ -n "$ZPCAT" ]] ; then # zpaq won't send anything to stdout; we have to cat "$DEST" RM "$DEST" elif [[ -z $KEEP ]] ; then RM "$FILE" fi return 0 } compress() { if [[ -z ${FILES[*]} ]] ; then # Compress stdin implies output to stdout ZPCAT=1 cat > "$TMP_FILE" if [[ $? == 0 ]] ; then compress_file "$TMP_FILE" "$TMP_ARCHIVE" [[ $? > 0 ]] && STATUS=1 fi else for f in ${FILES[@]} ; do declare DEST="${f}.zpaq" [[ -n $ZPCAT ]] && DEST="$TMP_ARCHIVE" if [[ -z $ZPCAT && -e $DEST && -z $FORCE ]] ; then echo "${DEST#${BASEDIR}/} exists, skipping" 1>&2 STATUS=1 else compress_file "$f" "$DEST" [[ $? > 0 ]] && STATUS=1 fi done fi } # If all of the command line files were disqualified because they # are compressed files, don't try to compress stdin. [[ -n $* && -z ${FILES[*]} ]] && exit 1 cleanup() { [[ -n $TMP_FILE && -f $TMP_FILE ]] && rm -f "$TMP_FILE" [[ -n $TMP_ARCHIVE && -f $TMP_ARCHIVE ]] && rm -f "$TMP_ARCHIVE" [[ -n $TMP_DIR && -d $TMP_DIR ]] && rm -rf "$TMP_DIR" } trap cleanup EXIT declare TMP_FILE=$(mktemp /tmp/wzpaqinXXXXXXXXX) declare TMP_DIR=$(mktemp -d /tmp/wzpaq_outXXXXXXXXX) declare TMP_ARCHIVE=$(mktemp /tmp/wzpaqXXXXXXXXX.zpaq) if [[ -n $DECOMPRESS ]] ; then decompress else compress fi exit $STATUS