#!/bin/bash

# This software is the proprietary property of The Regents of the University of California ("The Regents") Copyright (c)
# 1993-2006 The Regents of the University of California, Irvine campus. All Rights Reserved. 
# 
# Redistributions of source code must retain the above copyright
#     notice, this list of conditions and the following disclaimer.
# Redistributions in binary form must reproduce the above
#     copyright notice, this list of conditions and the following disclaimer
#     in the documentation and/or other materials provided with the
#     distribution.
# Neither the name of The Regents nor the names of its
#     contributors may be used to endorse or promote products derived from
#     this software without specific prior written permission.
# 
# The end-user understands that the program was developed for research
# purposes and is advised not to rely exclusively on the program for any
# reason.
# 
# THE SOFTWARE PROVIDED IS ON AN "AS IS" BASIS, AND THE REGENTS AND
# CONTRIBUTORS HAVE NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT,
# UPDATES, ENHANCEMENTS, OR MODIFICATIONS. THE REGENTS AND CONTRIBUTORS
# SPECIFICALLY DISCLAIM ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING BUT
# NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A
# PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE REGENTS OR
# CONTRIBUTORS BE LIABLE TO ANY PARTY FOR DIRECT, INDIRECT, SPECIAL,
# INCIDENTAL, EXEMPLARY OR CONSEQUENTIAL DAMAGES, INCLUDING BUT NOT
# LIMITED TO  PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES, LOSE OF USE,
# DATA OR PROFITS, OR BUSINESS INTERRUPTION, HOWEVER CAUSED AND UNDER ANY
# THEORY OF LIABILITY WHETHER IN CONTRACT, STRICT LIABILITY OR TORT
# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
# THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF ADVISED OF THE POSSIBILITY
# OF SUCH DAMAGE.

#et -x

# On using rsync for really efficient backups:
# http://www.mikerubel.org/computers/rsync_snapshots/

# On setting up a windows machine with an rsync daemon (service) :
# http://www.gaztronics.net/rsync.php

set -u

# We prefer /usr/local/bin over others, because homebrew rsync works, MacOS Sequoia rsync does not.
export PATH=/usr/local/bin:/bin:/usr/bin:/sbin:/usr/sbin

USE_COMPRESSION=False
RSYNC_OPTS=""
VERBOSE=0

function usage
{
    retval="$1"
    echo "Usage: $0"
    echo "    --srcdir /src/dir"
    echo "    --dstdir /dest/dir"
    echo "    --num-to-keep 365"
    echo "    --verbose"
    echo "    --rsync-opts rsopts"
    echo "    --use-compression"
    exit "$retval"
}

while [ "$#" -ge 1 ]
do
    case "$1" in
        --rsync-opts)
            RSYNC_OPTS="$2"
            shift
            ;;
        --srcdir)
            SRCDIR="$2"
            shift
            ;;
        --dstdir)
            DSTDIR="$2"
            shift
            ;;
        --num-to-keep)
            NUM_TO_KEEP="$2"
            shift
            ;;
        --verbose)
            VERBOSE=$((VERBOSE+1))
            ;;
        --use-compression)
            USE_COMPRESSION=True
            ;;
        --help|-h)
            usage 0
            ;;
        *)
            echo "$0: Unrecognized option: $1" 1>&2
            usage 1
            ;;
    esac
    shift
done

bad=False

if [ -z "${SRCDIR:-}" ]
then
    echo Sorry, you must give a source directory with --srcdir
    bad=True
fi

if [ -z "${DSTDIR:-}" ]
then
    echo Sorry, you must give a destination directory with --dstdir
    bad=True
fi

if [ -z "${NUM_TO_KEEP:-}" ]
then
    echo Sorry, you must give a number of backups to retain with --num-to-keep
    bad=True
fi

case "$bad" in
    True)
        echo "$0: One or more required options not specified" 1>&2
        usage 1
        ;;
    False)
        ;;
    *)
        echo "$0: Internal error: \$bad not True or False: $bad" 1>&2
        exit 1
        ;;
esac

case "$USE_COMPRESSION" in
    True)
        COMPRESS_FLAG="-z"
        ;;
    False)
        COMPRESS_FLAG=""
        ;;
    *)
        echo "$0: Internal error: \$USE_COMPRESSION not True or False: $USE_COMPRESSION" 1>&2
        exit 1
        ;;
esac

if ! mkdir -p "$DSTDIR"
then
    echo Sorry, mkdir -p "$DSTDIR" failed 1>&2
    exit 1
fi

if ! cd "$DSTDIR"
then
    echo Sorry, cd "$DSTDIR" failed 1>&2
    exit 1
fi

if [ -d "backup.$NUM_TO_KEEP.complete" ]
then
    # make room for some sliding, if the last directory already exists
    rm -rf "backup.$NUM_TO_KEEP.complete"
fi

if [ -d "backup.1.complete" ]
then
    # slide them up a notch
    for backup_no in $(seq $((NUM_TO_KEEP-1)) -1 1)
    do
        if [ "$VERBOSE" -ge 1 ]
        then
            echo Checking backup "$backup_no"...
        fi
        if [ -d "backup.$backup_no.complete" ]
        then
            if [ "$VERBOSE" -ge 1 ]
            then
                echo Sliding backup "$backup_no"...
            fi
            backup_no_plus_1="$((backup_no+1))"
            mv "backup.$backup_no.complete" "backup.$backup_no_plus_1.complete"
            if [ -d "backup.$backup_no" ]
            then
                (
                    echo "Both backup.$backup_no and backup.$backup_no.complete detected"
                    echo "Will keep backup.$backup_no.complete, but delete backup.$backup_no"
                ) 1>&2
                rm -rf "backup.$backup_no"
            fi
        fi
    done
    if ! mkdir backup.1
    then
        echo Sorry, mkdir backup.1 failed 1>&2
        exit 1
    fi
elif [ -d backup.1 ]
then
    (
        echo "Incomplete backup detected - instead of sliding backups up a notch, instead"
        echo "we'll try to get a complete backup using the prior incomplete one as"
        echo "a starting point..."
    ) 1>&2
else
    if ! mkdir backup.1
    then
        echo Sorry, mkdir backup.1 failed 1>&2
        exit 1
    fi
fi

move_it=False

# shellcheck disable=SC2086
if rsync \
    $RSYNC_OPTS \
    -a \
    $COMPRESS_FLAG \
    --delete \
    --link-dest=../backup.2.complete "$SRCDIR"/ backup.1
then
    move_it=True
else
    case "$?" in
        23)
            # This means some files gave permission denied - assume that's OK
            move_it=True
            ;;
        24)
            # This means one or more files disappeared
            move_it=True
            ;;
        *)
            echo rsync failed, not "mv'ing. Exit code was $?"
            ;;
    esac
fi

case "$move_it" in
    True)
        mv backup.1 backup.1.complete
        ;;
    False)
        exit 1
        ;;
    *)
        echo "$0: Internal error: \$move_it not True or False: $move_it" 1>&2
        exit 1
        ;;
esac