#!/bin/bash
# git-annex-remote-rclone - wrapper to enable use of rclone-supported cloud providers as git-annex special remotes.
#
# Install in PATH as git-annex-remote-rclone
#
# Copyright (C) 2016-2017  Daniel Dent
#
# This program is free software: you can redistribute it and/or modify it under the terms of version 3 of the GNU
# General Public License as published by the Free Software Foundation.
#
# 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.
#
# Based on work originally copyright 2013 Joey Hess which was licenced under the GNU GPL version 3 or higher.
#

set -e

# This program speaks a line-based protocol on stdin and stdout.
# When running any commands, their stdout should be redirected to stderr
# (or /dev/null) to avoid messing up the protocol.
runcmd () {
	"$@" >&2
}

# Gets a value from the remote's configuration, and stores it in RET
getconfig () {
	ask GETCONFIG "$1"
}

# Stores a value in the remote's configuration.
setconfig () {
	echo SETCONFIG "$1" "$2"
}

validate_layout() {
	if [ -z "$RCLONE_LAYOUT" ]; then
		RCLONE_LAYOUT="lower"
	fi
	case "$RCLONE_LAYOUT" in
		lower|directory|nodir|mixed|frankencase)
			;;
		*)
			echo "INITREMOTE-FAILURE rclone_layout setting not recognized"
			exit 1
			;;
	esac		
}

# Sets LOC to the location to use to store a key.
calclocation () {
	case "$RCLONE_LAYOUT" in
		lower)
			ask DIRHASH-LOWER "$1"
			LOC="$REMOTE_TARGET:$REMOTE_PREFIX/$RET"
			;;
		directory)
			ask DIRHASH-LOWER "$1"
			LOC="$REMOTE_TARGET:$REMOTE_PREFIX/$RET$1/"
			;;
		nodir)
			LOC="$REMOTE_TARGET:$REMOTE_PREFIX/"
			;;	
		mixed)
			ask DIRHASH "$1"
			LOC="$REMOTE_TARGET:$REMOTE_PREFIX/$RET"
			;;
		frankencase)
			ask DIRHASH "$1"
			lret=$(echo $RET|tr A-Z a-z)
			LOC="$REMOTE_TARGET:$REMOTE_PREFIX/$lret"
			;;
	esac
}

# Asks for some value, and stores it in RET
ask () {
	echo "$1" "$2"
	read -r resp
    # Strip trailing carriage return, if present
    resp="${resp%$'\r'}"
	if echo $resp|grep '^VALUE '>/dev/null; then
	    RET=$(echo "$resp" | cut -f2- -d' ')
    	else
	    RET=""
	fi
}


# This has to come first, to get the protocol started.
echo VERSION 1

while read -r line; do
    # Strip trailing carriage return, if present
    line="${line%$'\r'}"
	set -- $line
	case "$1" in
		INITREMOTE)
			# Do anything necessary to create resources
			# used by the remote. Try to be idempotent.
			# 
			# Use GETCONFIG to get any needed configuration
			# settings, and SETCONFIG to set any persistent
			# configuration settings.
			# 
			# (Note that this is not run every time, only when
			# git annex initremote or git annex enableremote is
			# run.)

			getconfig prefix
			REMOTE_PREFIX=$RET
			if [ -z "$REMOTE_PREFIX" ]; then
                		REMOTE_PREFIX="git-annex"
			fi
			if [ "$REMOTE_PREFIX" == "/" ]; then
		                echo INITREMOTE-FAILURE "storing objects directly in the root (/) is not supported"
			fi
			setconfig prefix $REMOTE_PREFIX

			getconfig target
			REMOTE_TARGET=$RET
			setconfig target $REMOTE_TARGET
			
			getconfig rclone_layout
			RCLONE_LAYOUT=$RET
			validate_layout
			setconfig rclone_layout $RCLONE_LAYOUT

			if [ -z "$REMOTE_TARGET" ]; then
		                echo INITREMOTE-FAILURE "rclone remote target must be specified (use target= parameter)"
			fi

            if runcmd rclone mkdir $REMOTE_TARGET:$REMOTE_PREFIX; then
                echo INITREMOTE-SUCCESS
            else
                echo INITREMOTE-FAILURE "Failed to create directory on remote. Ensure that 'rclone config' has been run."
            fi
		;;
		PREPARE)
			# Use GETCONFIG to get configuration settings,
			# and do anything needed to get ready for using the
			# special remote here.

			getconfig prefix
			REMOTE_PREFIX="$RET"
			
			getconfig target
			REMOTE_TARGET="$RET"
			
			getconfig rclone_layout
			RCLONE_LAYOUT="$RET"
			validate_layout

            echo PREPARE-SUCCESS
		;;
		TRANSFER)
			key="$3"
			file="$4"
			case "$2" in
				STORE)
					# Store the file to a location
					# based on the key.
					# XXX when at all possible, send PROGRESS
					calclocation "$key"
					if [ ! -e "$file" ]; then
						echo TRANSFER-FAILURE STORE "$key" "asked to store non-existent file $file"
					else	
						if runcmd rclone copy "$file" "$LOC"; then
							echo TRANSFER-SUCCESS STORE "$key"
						else
							echo TRANSFER-FAILURE STORE "$key"
						fi
					fi					
				;;
				RETRIEVE)
					# Retrieve from a location based on
					# the key, outputting to the file.
					# XXX when easy to do, send PROGRESS
					calclocation "$key"
					# http://stackoverflow.com/questions/31396985/why-is-mktemp-on-os-x-broken-with-a-command-that-worked-on-linux
					if GA_RC_TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/rclone-annex-tmp.XXXXXXXXX") &&
					    runcmd rclone copy "$LOC$key" $GA_RC_TEMP_DIR &&
					    mv $GA_RC_TEMP_DIR/$key $file &&
					    rmdir $GA_RC_TEMP_DIR; then
						echo TRANSFER-SUCCESS RETRIEVE "$key"
					else
						echo TRANSFER-FAILURE RETRIEVE "$key"
					fi
				;;
			esac
		;;
		CHECKPRESENT)
			key="$2"
			calclocation "$key"

			# Any nonzero object count means present.
			# Some rclone backends support multiple
			# files under one name.
			if check_result=$(rclone size "$LOC$key" 2>&1) &&
			    echo $check_result|grep -E 'Total objects: [1-9][0-9]* Total size' >&2 &&
			    ! echo $check_result|grep 'Total size: 0 (0 bytes)' >&2; then
				echo CHECKPRESENT-SUCCESS "$key"
			else
				# rclone 1.29 used 'Total objects: 0'
				# rclone 1.30 uses 'directory not found'
				if echo $check_result|grep 'Total objects: 0' >&2 || 
				   echo $check_result|grep ' directory not found' >&2; then 
					echo CHECKPRESENT-FAILURE "$key"
				else
					# When the directory does not exist,
					# the remote is not available.
					# (A network remote would similarly
					# fail with CHECKPRESENT-UNKNOWN
					# if it couldn't be contacted).
					echo CHECKPRESENT-UNKNOWN "$key" "remote currently unavailable or git-annex-remote-rclone failed to parse rclone output"
				fi
			fi
		;;
		REMOVE)
			key="$2"
			calclocation "$key"
			# Note that it's not a failure to remove a
			# key that is not present.
			if remove_result=$(rclone delete --retries 1 "$LOC$key" 2>&1); then
		                echo REMOVE-SUCCESS "$key"
	                else
	    		   	# rclone 1.29 used Failed to purge: Couldn't find directory:
				# rclone 1.30 used no such file or directory
				# rclone 1.33 uses directory not found
		                if echo $remove_result | grep " Failed to purge: Couldn't find directory: " >&2 ||
				   echo $remove_result | grep ' no such file or directory' ||
				   echo $remove_result | grep ' directory not found' >&2
				then   
					echo REMOVE-SUCCESS "$key"
		                else
                		        echo REMOVE-FAILURE "$key"
				fi
			fi
		;;
		*)
			# The requests listed above are all the ones
			# that are required to be supported, so it's fine
			# to say that any other request is unsupported.
			echo UNSUPPORTED-REQUEST
		;;
	esac	
done


# XXX anything that needs to be done at shutdown can be done here
