#!/bin/sh
# This is a demo git-annex external special remote program,
# which adds basic torrent download support to git-annex.
#
# Uses aria2c. Also needs the original bittorrent (or bittornado) for the
# btshowmetainfo command.
# 
# Install in PATH as git-annex-remote-torrent
#
# Enable remote by running:
#  git annex initremote torrent type=external encryption=none externaltype=torrent
#  git annex untrust torrent
#
# Copyright 2014 Joey Hess; 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 response and stores it in $RET
getvalue () {
	read resp
	# Tricky POSIX shell code to split first word of the resp,
	# preserving all other whitespace
	case "${resp%% *}" in
		VALUE)
			RET="$(echo "$resp" | sed 's/^VALUE \?//')"
		;;
		*)
		RET=""
		;;
	esac
}

# Get a list of all known torrent urls for a key,
# storing it in a temp file.
geturls () {
	key="$1"
	tmp="$2"

	echo GETURLS "$key"
	getvalue
	while [ -n "$RET" ]; do
		if istorrent "$RET"; then
			echo "$RET" >> "$tmp"
		fi
		getvalue
	done
}

# Does the url end in .torrent?
# Note that we use #N on the url to indicate which file
# from a multi-file torrent is wanted.
istorrent () {
	echo "$1" | egrep -q "\.torrent(#.*)?$"
}

# Download a single file from a torrent.
#
# Note: Does not support resuming interrupted transfers.
# Note: Does not feed progress info back to git-annex, and since
# the destination file is only populated at the end, git-annex will fail
# to display a progress bar for this download.
downloadtorrent () {
	torrent="$1"
	n="$2"
	dest="$3"

	tmpdir="$(mktemp -d)"

	# aria2c will create part of the directory structure
	# contained in the torrent. It may download parts of other files
	# in addition to the one we asked for. So, we need to find
	# out the filename we want, and look for it.
	wantdir="$(btshowmetainfo "$torrent" | grep "^directory name: " | sed "s/^directory name: //" || true)"
	if [ -n "$wantdir" ]; then
		wantfile="$(btshowmetainfo "$torrent" | grep '^   ' | sed 's/^   //' | head -n "$n" | tail -n 1 | sed 's/ ([0-9]*)$//')"
		if ! runcmd aria2c --select-file="$n" "$torrent" -d "$tmpdir"; then
			false
		fi
	else
		wantfile="$(btshowmetainfo "$torrent" | egrep "^file name.*: " | sed "s/^file name.*: //")"
		wantdir=.
		if ! runcmd aria2c "$torrent" -d "$tmpdir"; then
			false
		fi
	fi
	if [ -e "$tmpdir/$wantdir/$wantfile" ]; then
		mv "$tmpdir/$wantdir/$wantfile" "$dest"
		rm -rf "$tmpdir"
	else
		rm -rf "$tmpdir"
		false
	fi
}

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

while read line; do
	set -- $line
	case "$1" in
		INITREMOTE)
			echo INITREMOTE-SUCCESS
		;;
		PREPARE)
			echo PREPARE-SUCCESS
		;;
		CLAIMURL)
			url="$2"
			if istorrent "$url"; then
				echo CLAIMURL-SUCCESS
			else
				echo CLAIMURL-FAILURE
			fi
		;;
		CHECKURL)
			url="$2"
			# List contents of torrent.
			tmp=$(mktemp)
			if ! runcmd curl --proto -all,http,https -o "$tmp" "$url"; then
				echo CHECKURL-FAILURE
			else
				oldIFS="$IFS"
			IFS="
"
				printf "CHECKURL-MULTI"
				n=0
				for l in $(btshowmetainfo "$tmp" | grep '^   ' | sed 's/^   //'); do
					# Note that the file cannot contain spaces.
					file="$(echo "$l" | sed 's/ ([0-9]*)$//' | sed 's/ /_/g')"
					size="$(echo "$l" | sed 's/.* (\([0-9]*\))$/\1/')"
					n=$(expr $n + 1)
					printf " $url#$n $size $file"
				done
				if [ "$n" = 0 ]; then
					file="$(btshowmetainfo "$tmp" | egrep "^file name.*: " | sed "s/^file name.*: //")"
					size="$(btshowmetainfo "$tmp" | egrep "^file size.*: " | sed "s/^file size.*: \([0-9]*\).*/\1/")"
					printf " $url $size $file"
				fi
				printf "\n"
				IFS="$oldIFS"
			fi
			rm -f "$tmp"
		;;
		TRANSFER)
			op="$2"
			key="$3"
			shift 3
			file="$@"
			case "$op" in
				STORE)
					echo TRANSFER-FAILURE STORE "$key" "upload not supported"
				;;
				RETRIEVE)
					urltmp=$(mktemp)
					geturls "$key" "$urltmp"
					url="$(head "$urltmp")" || true
					rm -f "$urltmp"
					if [ -z "$url" ]; then
						echo TRANSFER-FAILURE RETRIEVE "$key" "no known torrent urls for this key"
					else
						tmp=$(mktemp)
						if ! runcmd curl --proto -all,http,https -o "$tmp" "$url"; then
							echo TRANSFER-FAILURE RETRIEVE "$key" "failed downloading torrent file from $url"
						else
							filenum="$(echo "$url" | sed 's/(.*#\(\d*\)/\1/')"
							if downloadtorrent "$tmp" "$filenum" "$file"; then
								echo TRANSFER-SUCCESS RETRIEVE "$key"
							else
								echo TRANSFER-FAILURE RETRIEVE "$key" "failed to download torrent contents from $url"
							fi
						fi
						rm -f "$tmp"					
					fi
				;;
			esac
		;;
		CHECKPRESENT)
			key="$2"
			# Let's just assume that torrents are never present
			# for simplicity.
			echo CHECKPRESENT-UNKNOWN "$key" "cannot reliably check torrent status"
		;;
		REMOVE)
			key="$2"
			# Remove all torrent urls for the key.
			tmp=$(mktemp)
			geturls "$key" "$tmp"
			for url in $(cat "$tmp"); do
				echo SETURLMISSING "$key" "$url"
			done
			rm -f "$tmp"
			echo REMOVE-SUCCESS "$key"
		;;
		*)
			echo UNSUPPORTED-REQUEST
		;;
	esac	
done