Astroport.ONE/tools/tusc

284 lines
9.1 KiB
Bash
Executable File

#!/usr/bin/env bash
#
# TUS client 1.0.0 protocol implementation for bash.
#
# Author:
# Jitendra Adhikari <jiten.adhikary@gmail.com>
#
# Be sure to check readme doc at https://github.com/adhocore/tusc.sh
#
if [[ -f $HOME/.tus.dbg ]]; then set -ex; else set -e; fi
FULL=$(readlink -f $0) TUSC=$(basename $0) SPINID=0 CURLARGS=
declare -A HEADERS # assoc headers of last request
declare ISOK=0 # is last request ok?
# message helpers
line() {
[[ $NOCOLOR ]] && echo -e "$1" || echo -e "\e[${3:-0};$2m$1\e[0m"
[[ "$4" == "" ]] || exit $4
}
error() { line "$1" 31 0 $2; }
ok() { line "${1:- Done}" 32 0 $2; }
info() { line "$1" 33 0 $2; }
comment() { line "$1" 30 1 $2; }
# show version
version() { echo v0.8.1; }
# update tusc
update()
{
NEWVER=`curl -sSL https://raw.githubusercontent.com/adhocore/tusc.sh/master/VERSION`
[[ "v$NEWVER" == "$(version)" ]] && ok "Already latest version" 0
info "Updating $TUSC ..."
curl -sSLo ${FULL} https://raw.githubusercontent.com/adhocore/tusc.sh/master/tusc.sh
ok " Done [${NEWVER}]"
}
# show usage
usage()
{
cat << USAGE
$TUSC $(info `version`) | $(ok "(c) Jitendra Adhikari")
$TUSC is bash implementation of tus-client (https://tus.io).
$(ok Usage:)
$TUSC <--options>
$TUSC <host> <file> [algo]
$(ok Options:)
$(info "-a --algo") $(comment "The algorigthm for key &/or checksum.")
$(comment "(Eg: sha1, sha256)")
$(info "-b --base-path") $(comment "The tus-server base path (Default: '/files/').")
$(info "-c --creds") $(comment "File with credentials; user and pass in shell syntax:")
$(line 'USER="my_user"' 36)
$(line 'PASS="my_pass"' 36)
$(info "-C --no-color") $(comment "Donot color the output (Useful for parsing output).")
$(info "-f --file") $(comment "The file to upload.")
$(info "-h --help") $(comment "Show help information and usage.")
$(info "-H --host") $(comment "The tus-server host where file is uploaded.")
$(info "-L --locate") $(comment "Locate the uploaded file in tus-server.")
$(info "-S --no-spin") $(comment "Donot show the spinner (Useful for parsing output).")
$(info "-u --update") $(comment "Update tusc to latest version.")
$(info " --version") $(comment "Print the current tusc version.")
$(ok Examples:)
$TUSC --help # shows this help
$TUSC --update # updates itself
$TUSC --version # prints current version of itself
$TUSC 0:1080 ww.mp4 # uploads ww.mp4 to http://0.0.0.0:1080/files/
$TUSC -H 0:1080 -f ww.mp4 # same as above
$TUSC -H 0:1080 -f ww.mp4 -a sha256 # same as above but uses sha256 algo for key/checksum
$TUSC -H 0:1080 -f ww.mp4 -b /store/ # uploads ww.mp4 to http://0.0.0.0:1080/store/
USAGE
}
# get/set tus config
tus-config() # $1 = key, $2 = value
{
TUSFILE="$HOME/.tus.json"
[[ -f $TUSFILE ]] || echo '{}' > $TUSFILE
TUSJSON=`cat $TUSFILE`
if [[ $# -eq 0 ]]; then
echo $TUSJSON
elif [[ $# -eq 1 ]]; then
echo $TUSJSON | jq -r "$1"
else
echo $TUSJSON | jq "$1=\"$2\"" > $TUSFILE
fi
}
# create a part of file
filepart() # $1 = start_byte, $2 = byte_length, $3 = file
{
dd bs=1M skip="$1" count="$2" iflag=skip_bytes ${3:+if="$3"} ${3:+of="$3.part"}
echo `realpath $3.part`
}
# http request
request()
{
echo > $HEADER
[[ $CREDS ]] && USERPASS="--basic --user '$USER:$PASS' "
[[ $DEBUG ]] && comment "> curl ${USERPASS//:$PASS/}-sSLD $HEADER -H 'Tus-Resumable: 1.0.0' --cookie $1"
BODY=$(bash -c "curl $USERPASS-sSLD $HEADER -H 'Tus-Resumable: 1.0.0' $CURLARGS --cookie $1") HEADERS=()
while IFS=':' read key value; do
if [[ "${key:0:5}" == "HTTP/" ]]; then
value=$(echo "$key" | grep -Eo '[0-9]{3}') key=Status
fi
value="${value/ /}" HEADERS[$key]="${value%$'\r'}"
done < <(cat "$HEADER")
if [[ "${HEADERS[Status]}" == "20"* ]]; then ISOK=1; else ISOK=0; fi
if [[ $ISOK -eq 0 ]] && [[ "$1" != *"--head"* ]]; then error "$BODY" 1; fi
}
# show spinner and mark its pid
spinner()
{
[[ $NOSPIN ]] && return 0
do-spin &
SPINID=$!
disown
}
# do spin (credits: https://www.shellscript.sh/tips/spinner/)
do-spin()
{
local chars="+/|\\-+/|\\-"
while :; do
for i in `seq 0 9`; do
echo -n "${chars:$i:1}" && echo -en "\010" && sleep 0.1
done
done
}
no-spinner()
{
[[ $NOSPIN ]] && return 0
local PID=$SPINID
SPINID=0
[[ $PID -eq 0 ]] || kill $PID 2> /dev/null
}
# exit handler
on-exit()
{
no-spinner
rm -f $FILE.part $HEADER0 $HEADER
[[ $OFFSET ]] || return 0
OFFSET=${HEADERS[Upload-Offset]:-0} LEFTOVER=$((SIZE - OFFSET))
if [[ $LEFTOVER -eq 0 ]]; then
ok "✔ Uploaded successfully!"
else
error "✖ Unfinished upload, please rerun the command to resume." 1
fi
info "URL: $TUSURL"
}
main()
{
# argv parsing
while [[ $# -gt 0 ]]; do
case "$1" in
-a | --algo) SUMALGO="$2"; shift 2 ;;
-b | --base-path) BASEPATH="$2"; shift 2 ;;
-c | --creds) CREDS="$2"; shift 2 ;;
-C | --no-color) NOCOLOR=1; shift ;;
-f | --file) FILE="$2"; shift 2 ;;
-h | --help | help) usage $1; exit 0 ;;
-H | --host) HOST="$2"; shift 2 ;;
-L | --locate) LOCATE=1; shift ;;
-S | --no-spin) NOSPIN=1; shift ;;
-p | --psitransfer) PSITRANSFER=1; shift ;;
-u | --update) update; exit 0 ;;
--version | version) version; exit 0 ;;
--) shift; CURLARGS=$@; break ;;
*) if [[ $HOST ]]; then
if [[ $FILE ]]; then SUMALGO="${SUMALGO:-$1}"; else FILE="$1"; fi
else HOST=$1; fi
shift ;;
esac
done
trap on-exit EXIT
[[ $CREDS ]] && { [[ -f $CREDS ]] && source $CREDS && [[ $PASS ]] || error "--creds file couldn't be loaded" 1; }
[[ $HOST ]] || [[ $LOCATE ]] || error "--host required" 1
[[ $FILE ]] || error "--file required" 1
[[ -f $FILE ]] || error "--file doesn't exist" 1
SUMALGO=${SUMALGO:-sha1}
[[ $SUMALGO == "sha"* ]] || error "--algo '$SUMALGO' not supported" 1
FILE=`realpath $FILE` NAME=`basename $FILE` SIZE=`stat -c %s $FILE` MTIME=`stat -c %Y $FILE`
HEADER=`mktemp -t tus.XXXXXXXXXX`
# calc &/or cache key and checksum
KEY=`tus-config ".[\"$FILE:$MTIME\"].$SUMALGO?"`
[[ "null" == "$KEY" ]] && [[ $DEBUG ]] && comment "> ${SUMALGO}sum $FILE"
[[ "null" == "$KEY" ]] && spinner && read -r KEY _ <<< `${SUMALGO}sum $FILE` && no-spinner
tus-config ".[\"$FILE:$MTIME\"].$SUMALGO" "$KEY"
CHKSUM="$SUMALGO $(echo -n $KEY | base64 -w 0)"
[[ $DEBUG ]] && info "HOST : $HOST\nHEADER: $HEADER\nFILE : $NAME\nSIZE : $SIZE\nKEY : $KEY\nCHKSUM: $CHKSUM"
# head request
TUSURL=`tus-config ".[\"$KEY\"].location?"`
[[ $LOCATE ]] && info "URL: $TUSURL" && [[ $TUSURL != "null" ]]; [[ $LOCATE ]] && exit $?
[[ $TUSURL ]] && [[ "null" != "$TUSURL" ]] && request "--head $TUSURL"
if [[ "null" != "$TUSURL" ]] && [[ $ISOK -eq 1 ]]; then
OFFSET=${HEADERS[Upload-Offset]} LEFTOVER=$((SIZE - OFFSET))
[[ $LEFTOVER -eq 0 ]] && exit 0
[[ $DEBUG ]] && comment "> filepart $OFFSET $LEFTOVER $FILE"
spinner && FILEPART=`filepart $OFFSET $LEFTOVER $FILE` && no-spinner
# create request
else
OFFSET=0 LEFTOVER=$SIZE FILEPART=$FILE
META="filename $(echo -n $NAME | base64 -w 0)"
[[ $CREDS ]] && META="$META,user $(echo -n $USER | base64 -w 0)"
if [[ -v PSITRANSFER ]];
then
SID=$(cat /proc/sys/kernel/random/uuid | awk -F- '{print $5}')
META="$META,sid $(echo -n $SID | base64 -w 0)"
META="$META,retention $(echo -n 604800 | base64 -w 0)"
META="$META,password $(echo -n $PASS | base64 -w 0)"
META="$META,name $(echo -n $NAME | base64 -w 0)"
META="$META,comment $(echo -n $COMMENT | base64 -w 0)"
META="$META,type $(echo -n $TYPE | base64 -w 0)"
fi
request "-H 'Upload-Length: $SIZE' \
-H 'Upload-Key: $KEY' \
-H 'Upload-Checksum: $CHKSUM' \
-H 'Upload-Metadata: $META' \
-X POST $HOST${BASEPATH:-/files/}"
TUSURL=${HEADERS[Location]}
[[ $TUSURL ]] || error "Tus server replied with empty location. Try changing --base-path param." 1
# save location config
tus-config ".[\"$KEY\"].location" "$TUSURL"
fi
[[ -v PSITRANSFER ]] && TUSURL="$HOST$TUSURL"
# patch request
request "-H 'Content-Type: application/offset+octet-stream' \
-H 'Content-Length: $LEFTOVER' \
-H 'Upload-Checksum: $CHKSUM' \
-H 'Upload-Offset: $OFFSET' \
`[[ ! -v PSITRANSFER ]] && echo "-H 'Transfer-Encoding: chunked'"` \
--upload-file '$FILEPART' \
--request PATCH $TUSURL" &
# show spinner
spinner
HEADER0=$HEADER HEADER=`mktemp -t tus.XXXXXXXXXX`
while :; do
# psitransfer backend does not set Upload-Offset header
if [[ -v PSITRANSFER ]] && [[ -z ${HEADERS[Upload-Offset]} ]];
then
HEADERS[Upload-Offset]=$SIZE
fi
[[ ${HEADERS[Upload-Offset]} -eq $SIZE ]] && exit
request "--head $TUSURL" > /dev/null
[[ ${HEADERS[Upload-Offset]} -eq $SIZE ]] || sleep 2
done
}
main "$@"