#!/bin/sh
# 
# synchrone - main executable for synchrone
# 
#    synchrone - simple wrapper for rsync(1)

# we keep our files in this directory:

SYNCHRONE_DIR=/etc/synchrone

# hardcoded filenames

CONFIG_DIR="$SYNCHRONE_DIR"/config
RULES_DIR="$SYNCHRONE_DIR"/rules
TIMESTAMP_DIR="$SYNCHRONE_DIR"/timestamp

CONFIG_FILENAME=global
RULES_FILENAME=main	# BUG: there can be no spaces in *this* filename!
UPGRADE_FILENAME=upgrade
TIMESTAMP_FILENAME=current

# absolute paths

CONFIG="$CONFIG_DIR"/"$CONFIG_FILENAME"
RULES="$RULES_DIR"/"$RULES_FILENAME"
UPGRADE="$RULES_DIR"/"$UPGRADE_FILENAME"

# we'll deal with timestamps further on
# so we do not use TIMESTAMP variable at all

# these are commands used by synchrone:

LOGGER=`which logger`
RSYNC=`which rsync`
DATE=`which date`
CMP=`which cmp`
MV=`which mv`

# this is not used at all now:

IP=/sbin/ip

# synchrone return values

EXIT_OK=0
EXIT_RSYNC_ERROR=1
EXIT_USAGE_ERROR=2
EXIT_LOCAL_ERROR=3

# synchrone local functions

msg_log ()
{
	echo "$@" | "$LOGGER" -s -p user.notice -t "synchrone"
}

msg ()
{
	echo "$@" 1>&2
}

getmyip ()
{
	# this one is not used so far
	MY_ADDRESS=`$IP -oneline -family inet addr show | sed -rn '/eth/s+^.*inet ([^/]*).*$+\1+p'`
}

print_usage ()
{
	echo "Usage: $0 { --get | --put | --stamp | --upgrade }"
	echo "          [ --target=DIR ] [ SERVER_OPTIONS ] [ --profile-name=STRING ]"
	echo "          [ --do-nothing ] [ --force ] [ --verbose ] [ --stats ] [ --progress ] [ --dry ]"
	echo "          [ --usage | --help ]"
}

collect_rsync_options ()
{
	RSYNC_OPTS=

	# defaults
	RSYNC_OPTS+=" --archive --delete"

	# quiet mode
	if [ "$VERBOSE" -ne "1" ]; then
		RSYNC_OPTS+=" --quiet"
	fi

	# stats
	if [ "$STATS" -eq "1" ]; then
		RSYNC_OPTS+=" --stats"
	fi

	# progress
	if [ "$PROGRESS" -eq "1" ]; then
		RSYNC_OPTS+=" --progress"
	fi

	# dry run
	if [ "$DRY" = "1" ]; then
		RSYNC_OPTS+=" --dry-run"
	fi

	# these cannot be set with synchrone options:

	# timeout (sec)
	RSYNC_TIMEOUT=120
	#RSYNC_OPTS+=" --timeout="$RSYNC_TIMEOUT

	# i/o speed limit (kBps)
	RSYNC_LIMIT=1024
	#RSYNC_OPTS+=" --bwlimit="$RSYNC_LIMIT

	# compressing
	#RSYNC_OPTS+=" --compress"

	# delete-WHEN options (before by rsync default)
	#RSYNC_OPTS+=" --delete-before"
	RSYNC_OPTS+=" --delete-during"
	#RSYNC_OPTS+=" --delete-after"
}

read_config ()
{
	if [ -r "$CONFIG" ]; then
		msg "Reading configuration file: $CONFIG"
		. "$CONFIG"
	else
		msg "Configuration file $CONFIG not found, continuing anyway..."
	fi
}

parse_cmdline_options ()
{
	# defaults:
	VERBOSE="0"
	DRY="0"
	REAL="1"
	STATS="0"
	PROGRESS="0"
	FORCE="0"
	ACTION=

	while [ $# -gt 0 ]; do
		case "$1" in
			--get)
				new_action
				ACTION=get
				;;
			--put)
				new_action
				ACTION=put
				;;
			--stamp)
				new_action
				ACTION=stamp
				;;
			--upgrade)
				new_action
				ACTION=upgrade
				;;
			--target=*)
				TARGET="${1#--target=}"
				;;
			--server-address=*)
				SERVER_ADDRESS="${1#--server-address=}"
				;;
			--server-port=*)
				SERVER_PORT="${1#--server-port=}"
				;;
			--server-pub=*)
				SERVER_PUB="${1#--server-pub=}"
				;;
			--server-upload=*)
				SERVER_UPLOAD="${1#--server-upload}"
				;;
			--upload-user=*)
				UPLOAD_USER="${1#--upload-user}"
				;;
			--profile=*)
				PROFILE_NAME="${1#--profile=}"
				;;
			--do-nothing)
				REAL="0"
				;;
			--force)
				FORCE="1"
				;;
			--verbose)
				VERBOSE="1"
				;;
			--stats)
				STATS="1"
				;;
			--progress)
				PROGRESS="1"
				;;
			--dry)
				DRY="1"
				;;
			--usage)
				print_usage
				exit $EXIT_OK
				;;
			--help)
				# in fact, --help should differ from --usage
				# but it is not implemented so far (TODO)
				print_usage
				exit $EXIT_OK
				;;
			*)
				msg "Syntax error, use --help for usage information."
				exit $EXIT_USAGE_ERROR
				;;
		esac
		shift
	done
}

new_action ()
{
	if [ -n "$ACTION" ]; then
		msg "Too many actions at a time. Try using only one of them?"
		exit $EXIT_USAGE_ERROR
	fi
}

print_info ()
{
	msg "Target directory is $TARGET."
	msg "Server is located at $SERVER_ADDRESS:$SERVER_PORT."
	msg_log "Using profile \`$PROFILE_NAME'."
	msg_log "Starting action: $ACTION..."
}

add_rules ()
{
	# rule list
	# see rsync(1) manpage for details on syntax and usage
	RSYNC_OPTS+=" --filter=merge_$1"
	if [ -r "$1" ]; then
		msg "Rules are found in \`$1'."
	else
		msg "Cannot read rules file \`$1', exiting."
		exit $EXIT_USAGE_ERROR
	fi
}

check_reality ()
{
	if [ "$REAL" -ne "1" ]; then
		exit $EXIT_OK
	fi
}

update_profile ()
{
	"$RSYNC" $RSYNC_OPTS "${SERVER_DOWN}/${PROFILE_NAME}/" "$TARGET"
	if [ "$?" -eq 0 ]; then
		msg_log "Local profile successfully updated."
		"$MV" "$NEW_TIMESTAMP" "$CURRENT_TIMESTAMP"
		if [ "$?" -eq 0 ]; then
			exit $EXIT_OK
		else
			msg_log "Cannot update local timestamp."
			exit $EXIT_LOCAL_ERROR
		fi
	else
		msg_log "Error while updating local profile, leaving $CURRENT_TIMESTAMP untouched."
		exit $EXIT_RSYNC_ERROR
	fi
}

# well, now we finish with definitions
# and get down to work

read_config

parse_cmdline_options "$@"

# we deal with slashes manually
# as far as they are meaningful to rsync

TARGET="${TARGET%/}"
if [ -n "$TARGET" ]; then
	TARGET_S="$TARGET"/
else
	TARGET="/"
	TARGET_S="/"
fi

#getmyip

print_info

collect_rsync_options

# secondary variables setup:

# server directories

SERVER_DOWN="rsync://${SERVER_ADDRESS}:${SERVER_PORT}${SERVER_PUB}"
SERVER_UP="rsync://${UPLOAD_USER}@${SERVER_ADDRESS}:${SERVER_PORT}${SERVER_UPLOAD}"

# server-side timestamp prefix

TIMESTAMP_PREFIX="timestamp."

# timestamp filename for a specific profile

PROFILE_TIMESTAMP="${TIMESTAMP_PREFIX}${PROFILE_NAME}"

# target file for downloaded timestamp

NEW_TIMESTAMP="${TIMESTAMP_DIR}/${PROFILE_TIMESTAMP}"

# local timestamp filename

CURRENT_TIMESTAMP="${TIMESTAMP_DIR}/${TIMESTAMP_FILENAME}"

# and now we go straight to our job

if [ "$ACTION" = "upgrade" ]; then

	# some prerequisites, precautions and tests
	msg "> \"$RSYNC\" \"${SERVER_DOWN}/${PROFILE_NAME}${UPGRADE}\" \"${UPGRADE}\""
	check_reality
	# first we should download up-to-date upgrade rules
	"$RSYNC" "${SERVER_DOWN}/${PROFILE_NAME}${UPGRADE}" "${UPGRADE}"
	if [ "$?" -ne 0 ]; then
		msg_log "Error while downloading upgrade rules, aborted."
		exit $EXIT_RSYNC_ERROR
	fi
	# then we give them to rsync to upgrade synchrone
	add_rules "$UPGRADE"
	msg "> \"$RSYNC\" $RSYNC_OPTS \"${SERVER_DOWN}/${PROFILE_NAME}/\" \"${TARGET}\""
	"$RSYNC" $RSYNC_OPTS "${SERVER_DOWN}/${PROFILE_NAME}/" "${TARGET}"
	if [ "$?" -ne 0 ]; then
		msg_log "Error while upgrading, aborted."
		exit $EXIT_RSYNC_ERROR
	fi
	msg_log "Upgraded successfully."
	exit $EXIT_OK

elif [ "$ACTION" = "get" ]; then

	# some prerequisites, precautions and tests
	add_rules "$RULES"
	msg "> \"$RSYNC\" \"${SERVER_DOWN}/${PROFILE_TIMESTAMP}\" \"$NEW_TIMESTAMP\""
	check_reality
	# download timestamp first (anyway)
	"$RSYNC" "${SERVER_DOWN}/${PROFILE_TIMESTAMP}" "$NEW_TIMESTAMP"
	if [ "$?" -ne 0 ]; then
		msg_log "Error while downloading timestamp, aborted."
		exit $EXIT_RSYNC_ERROR
	fi
	# if there is --force specified, then don't compare timestamps
	if [ "$FORCE" -eq "1" ]; then
		update_profile
	fi
	LOCAL_MARK="`cat \"$CURRENT_TIMESTAMP\"`"
	NEW_MARK="`cat \"$NEW_TIMESTAMP\"`"
	if [ ${LOCAL_MARK:-0} -eq ${NEW_MARK:-30000101000000} ]; then
		msg_log "Equal timestamps, withdrawing."
		exit $EXIT_OK
	elif [ ${LOCAL_MARK:-0} -gt ${NEW_MARK:-30000101000000} ]; then
		msg_log "Local timestamp is more recent than server-side one, withdrawing."
		exit $EXIT_OK
	else
		# in this case we are to update our profile by executing rsync for TARGET
		update_profile
	fi

elif [ "$ACTION" = "put" ]; then

	# some prerequisites, precautions and tests
	add_rules "$RULES"
	msg "> \"$RSYNC\" $RSYNC_OPTS \"$TARGET_S\" \"${SERVER_UP}/${PROFILE_NAME}\""
	check_reality
	# upload local profile with rsync
	"$RSYNC" $RSYNC_OPTS "$TARGET_S" "${SERVER_UP}/${PROFILE_NAME}"
	if [ "$?" -eq 0 ]; then
		msg_log "Profile \`$PROFILE_NAME' updated successfully."
		msg "Use --stamp to update appropriate timestamp."
		exit $EXIT_OK
	else
		msg_log "Error while updating profile \`${PROFILE_NAME}'. You had better try to update it once more!"
		exit $EXIT_RSYNC_ERROR
	fi

elif [ "$ACTION" = "stamp" ]; then

	# generate a timestamp
	"$DATE" +'%Y%m%d%H%M' > "$CURRENT_TIMESTAMP"
	if [ "$?" -ne 0 ]; then
		msg_log "Cannot create new timestamp, exiting."
		exit $EXIT_LOCAL_ERROR
	fi
	# and upload it to the server
	"$RSYNC" "$CURRENT_TIMESTAMP" "${SERVER_UP}/${PROFILE_TIMESTAMP}"
	if [ "$?" -ne 0 ]; then
		msg_log "Cannot update profile timestamp, exiting."
		exit $EXIT_RSYNC_ERROR
	else
		msg_log "Profile timestamp updated successfully."
		exit $EXIT_OK
	fi

elif [ -n "$ACTION" ]; then

	msg "Unknown action, exiting."
	exit $EXIT_USAGE_ERROR

else

	msg "Action not specified, exiting."
	exit $EXIT_USAGE_ERROR

fi

# note: we should not reach this line normally
# (we should have already exited)

