#! /bin/sh
# Copyright (c) 2012 Olivier Schwander <olivier.schwander@ens-lyon.org>
#
# 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 3 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 <http://www.gnu.org/licenses/>.

VERSION=0.1

set -e

RSYNC="rsync -aP"

HASH=sha256
FILES="ARCHIVES *SUMS META REMOTES"
DIRECTORY="."

usage() {
    echo "Usage: $0 [OPTIONS] COMMAND [ARGS]"
    echo
    echo "Commands:"
    echo "  list                     List the archived files"
    echo "  add SOURCE [NAME]        Add the SOURCE file or directory"
    echo "                           under the name NAME if provided"
    echo "  check [HASH] ... [HASH]  Check the integrity of the archives"
    echo "                           using the given hash methods"
    echo "  hash [HASH]              Compute the hash values for HASH"
    echo "  push [REMOTE]            Push the repository to the remote"
    echo "                           repository REMOTE or to all the"
    echo "                           configured remotes if not provided"
    echo
    echo "Options:"
    echo "  -d ARCHIVE_PATH          Path to the archive"
    echo "  -h                       Display this help screen"
    echo "  -v                       Display version and licence informations"
}

version() {
    echo "keepsafe $VERSION"
    echo "Copyright (C) 2012 Olivier Schwander <olivier.schwander@ens-lyon.org>"
    echo "This program comes with ABSOLUTELY NO WARRANTY."
    echo "This is free software, and you are welcome to redistribute it"
    echo "under certain conditions."
}

# Outputs the content of a file if it is readable or an empty string
cat_r() {
    [ -r $1 ] && cat $1
}

# Given a list of files, outputs only the ones which are readable
filter() {
    OUT=""
    for x in $*; do
        [ -r $x ] && OUT="$OUT $x"
    done
    echo $OUT
}

# Display an error message and exits with the given error code
error() {
    echo $1 1>&2
    exit $2
}

# Returns the list of all archives files
list() {
    cat_r ARCHIVES
}

# Returns the name of the hash used in the archive repository
find_hashes() {
    (ls *SUMS 2>/dev/null || echo $HASH) | tr A-Z a-z | sed s/sums$//
}

add() {
    if [[ "$in" != /* ]]; then
        in=$1
    else
        in=$CWD/$in
    fi

    if [ -z "$2" ]; then
        name=$in
    else
        name=$2
    fi

    if [ -d $in ]; then
        archive=$(basename $name).tar.gz
    else
        archive=$(basename $name)
    fi

    if grep -w $archive ARCHIVES >/dev/null 2>&1; then
        error "Archive $archive already added" 1
    fi

    if [ -e $archive ]; then
        error "File $archive already existing" 1
    fi

    if [ -d $in ]; then
        if tar -zcvf $archive -C $CWD $in; then
            echo $archive >>ARCHIVES
            hash=$(find_hashes)
            for h in $hash; do
                hash $h
            done
            echo "Archive $archive successfully added"
        else
            error "Error when adding $archive" 1
        fi
    else
        if cp $in $archive; then
            echo $archive >>ARCHIVES
            hash=$(find_hashes)
            for h in $hash; do
                hash $h
            done
            echo "Archive $archive successfully added"
        else
            error "Error when adding $archive" 1
        fi
    fi
}

hash() {
    hash=$1
    hashbin=${hash}sum
    hashfile=$(echo $hashbin | tr a-z A-Z)S
    if ! which $hashbin >/dev/null; then
        echo "Binary $hashbin is missing, no way to compute $hash sums."
        exit 1
    fi

    if [ -r $hashfile ]; then
        missing_hash=$(awk '{print $2}' $hashfile | grep -v -w -f - ARCHIVES || true)
    else
        missing_hash=$(cat_r ARCHIVES)
    fi

    if [ -z "$missing_hash" ]; then
        echo "Nothing to hash with $hash."
        exit 0
    fi

    for archive in $missing_hash; do
        echo -n "Hashing $archive with $hash..."
        $hashbin $archive >>$hashfile
        echo "done."
    done
}

check() {
    hash=$1
    hashbin=${hash}sum
    hashfile=$(echo $hashbin | tr a-z A-Z)S

    if ! which $hashbin >/dev/null; then
        echo "Binary $hashbin is missing, no way to check $hash sums."
        exit 1
    fi

    if ! [ -r $hashfile ]; then
        echo "No hash file available for $hash, exiting."
        exit 1
    fi

    missing=""
    missing_hash=$(awk '{print $2}' $hashfile | grep -v -w -f - ARCHIVES || true)
    for archive in $missing_hash; do
        echo "No $hash hash for $archive."
        missing=yes
    done

    if [ -n "$missing" ]; then
        echo "You should run 'ks hash $hash' to fix missing hash but only if you are sure of the integrity of the archives."
    fi

    echo "Checking archives integrity... "
    if $hashbin -c $hashfile; then
        echo "Everything is ok ($hash)."
    else
        echo
        echo "Something get wrong !!!"
        echo "Hash mismatch for $hash."
        echo "WARNING: You should REALLY avoid to push this archive to another host and check your hard drive or filesystem."
        exit 255
    fi
}

push() {
    for DEST in $1; do
        $RSYNC $(filter $FILES) $(cat_r META) $(cat_r ARCHIVES) $DEST
    done
}

while getopts "vhd:" flag; do
    case "$flag" in
        v)
            version
            exit 0
            ;;
        h)
            usage
            exit 0
            ;;
        d)
            DIRECTORY=$OPTARG
            ;;
    esac
done

CWD=$(pwd)
if [ -d $DIRECTORY ]; then
    cd $DIRECTORY
else
    error "The directory $DIRECTORY does not exist" 2
fi

shift $(($OPTIND-1))

if ! [ $# -ge 1 ]; then
    echo "No command provided" 2>/dev/null
    usage
fi

case "$1" in
    list)
        list
        ;;
    add)
        shift
        if [ $# -ge 1 ]; then
            add $1 $2
        else
            error "Missing argument to add" 2
        fi
        ;;
    check)
        shift
        if [ $# -ge 1 ]; then
            hash=$*
        else
            hash=$(find_hashes)
        fi

        for h in $hash; do
            check $h
        done
        ;;
    hash)
        shift
        if [ $# -ge 1 ]; then
            hash $1
        else
            error "hash: missing argument" 2
        fi
        ;;
    push)
        check $HASH

        shift
        if [ $# -ge 1 ]; then
            REMOTES=$1
        else
            if [ -r REMOTES ]; then
                REMOTES=$(cat REMOTES)
            else
                error "No remote destination provided and empty REMOTES file" 2
            fi
        fi
        echo "Now we can push to $REMOTES:"
        push $REMOTES
        ;;
esac

