Skip to content

Commit b17ebdf

Browse files
committed
Implement --download
* Implemented ada --download function, including checksum verification * Created get_checksum function, to get a single checksum * Created get_local_checksum function, to get a checksum of a local file * Fixed rogue header from a curl command * Removed --verbose from curl when debugging; output is too overwhelming
1 parent c97c9a1 commit b17ebdf

File tree

1 file changed

+170
-13
lines changed

1 file changed

+170
-13
lines changed

ada/ada

Lines changed: 170 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -521,6 +521,12 @@ get_args() {
521521
target="$3"
522522
shift ; shift ; shift
523523
;;
524+
--download )
525+
command='download'
526+
source="$2"
527+
target="$3"
528+
shift ; shift ; shift
529+
;;
524530
--setlabel )
525531
command='setlabel'
526532
path="$2"
@@ -1063,7 +1069,6 @@ get_webdav_door () {
10631069
command='$debug && set -x
10641070
curl "${curl_authorization[@]}" \
10651071
"${curl_options_common[@]}" \
1066-
-H "Authorization: bearer $BEARER_TOKEN" \
10671072
-H "Accept: application/json" \
10681073
"$api_server/scripts/config.js" \
10691074
| jq -r ".\"dcache-view.endpoints.webdav\"" '
@@ -1737,6 +1742,55 @@ get_checksums () {
17371742
}
17381743

17391744

1745+
get_checksum () {
1746+
# Prints the requested checksum of a dCache file; nothing else.
1747+
# If the checksum of that type is not in the dCache database, print nothing.
1748+
local type="$1"
1749+
local path="$2"
1750+
encoded_path=$(urlencode "$path")
1751+
# Make sure the checksum type is something that dCache understands
1752+
case $type in
1753+
adler32 | ADLER32 )
1754+
type=ADLER32
1755+
;;
1756+
md5* | MD5* )
1757+
type=MD5_TYPE
1758+
;;
1759+
* )
1760+
echo 1>&2 "ERROR: checksum type '$type' is unsupported."
1761+
exit 1
1762+
;;
1763+
esac
1764+
# We output only the requested checksum, nothing else.
1765+
# If the requested checksum for this file is not in the dCache database,
1766+
# the result will be empty.
1767+
curl "${curl_authorization[@]}" \
1768+
"${curl_options_no_errors[@]}" \
1769+
-X GET "$api/namespace/$encoded_path?checksum=true" \
1770+
| jq -r ".checksums[] | select(.type == \"$type\") | .value"
1771+
}
1772+
1773+
1774+
get_local_checksum () {
1775+
# prints the checksum of a local file (not in dCache).
1776+
local type="$1"
1777+
local file="$2"
1778+
case $type in
1779+
adler32 | ADLER32 )
1780+
# This might be the most portable way to calculate an Adler32 checksum.
1781+
python3 -c "import zlib,sys;print(format(zlib.adler32(open('$file','rb').read()) & 0xffffffff,'08x'))"
1782+
;;
1783+
md5* | MD5* )
1784+
md5sum --quiet "$file"
1785+
;;
1786+
* )
1787+
echo 1>&2 "ERROR: checksum type '$type' is unsupported."
1788+
exit 1
1789+
;;
1790+
esac
1791+
}
1792+
1793+
17401794
get_channel_by_name () {
17411795
local channel_name="$1"
17421796
# Many other API calls depend on this one.
@@ -2388,13 +2442,13 @@ validate_input() {
23882442
exit 1
23892443
fi
23902444
;;
2391-
upload )
2445+
upload | download )
23922446
if [[ -z $source || $source == --* ]] ; then
2393-
echo 1>&2 "ERROR: please specify a source file for your upload."
2447+
echo 1>&2 "ERROR: please specify a source file for your $command."
23942448
exit 1
23952449
fi
23962450
if [[ -z $target || $target == --* ]] ; then
2397-
echo 1>&2 "ERROR: please specify a target for your upload."
2451+
echo 1>&2 "ERROR: please specify a target for your $command."
23982452
exit 1
23992453
fi
24002454
;;
@@ -2753,10 +2807,6 @@ api_call () {
27532807
$debug && echo 1>&2 "User specified WebDAV door: $webdav_door"
27542808
fi
27552809
# We should be ready to upload now!
2756-
# If --debug was specified, run curl with --verbose.
2757-
if $debug ; then
2758-
curl_args+=(--verbose)
2759-
fi
27602810
$debug && set -x # If --debug is specified, show curl command (tracing on)
27612811
curl "${curl_authorization[@]}" \
27622812
--location --post302 \
@@ -2766,12 +2816,119 @@ api_call () {
27662816
{ set +x ; } 2>/dev/null # Silently switch off tracing
27672817
;;
27682818
download )
2769-
# Check source (remote)
2770-
# - Is file? Then OK
2819+
#
27712820
# Check target (local)
2772-
# - Is file? Overwrite or not?
2773-
# - Is dir? Use remote file name
2774-
# - Does not exist? Check if parent is an existing dir, then OK, use remote file name
2821+
# We check the target first because it is local and thus faster.
2822+
#
2823+
curl_target=()
2824+
# If a file already exists, quit.
2825+
if [[ -f $target ]] ; then
2826+
echo 1>&2 "ERROR: Target file '$target' already exists."
2827+
exit 1
2828+
fi
2829+
# If the target is a directory, we tell curl to use the
2830+
# remote filename
2831+
if [[ -d $target ]] ; then
2832+
real_target="${target%/}/$(basename "$source")"
2833+
if [[ -e "$real_target" ]] ; then
2834+
echo 1>&2 "ERROR: destination '$real_target' already exists."
2835+
exit 1
2836+
fi
2837+
fi
2838+
# If the target does not exist, it's OK if the parent dir exists.
2839+
if [[ ! -e $target ]] ; then
2840+
parent=$(dirname "$target")
2841+
if [[ ! -d $parent ]] ; then
2842+
echo "ERROR: Can't download to '$target': directory '$parent' does not exist."
2843+
exit 1
2844+
fi
2845+
real_target="$target"
2846+
fi
2847+
curl_target=(--output "$real_target")
2848+
#
2849+
# Check source (remote).
2850+
#
2851+
# Has the user prepended a WebDAV door to the source? Split it.
2852+
if [[ "$source" == *"://"* ]]; then
2853+
webdav_door="${source%%//*}://${source#*//}"
2854+
webdav_door="${webdav_door%%/*}"
2855+
path="/${source#*://*/}"
2856+
else
2857+
webdav_door=""
2858+
path="$source"
2859+
fi
2860+
# Does the source (remote path) exist?
2861+
if ! exists "$path" ; then
2862+
echo 1>&2 "ERROR: source file '$path' does not exist."
2863+
exit 1
2864+
fi
2865+
# The source path should be a file, not a dir or a link.
2866+
case $(pathtype "$path") in
2867+
DIR )
2868+
echo 1>&2 "Source '$path' is a directory. Ada does not support downloading a directory." \
2869+
"You can use Rclone for that."
2870+
exit 1
2871+
;;
2872+
LINK )
2873+
echo 1>&2 "ERROR: target '$path' is a symlink. A symlink can point to a file or a directory." \
2874+
"Ada does not support downloading from a symlink."
2875+
exit 1
2876+
;;
2877+
esac
2878+
# If the source file is only on tape and not online,
2879+
# dCache would automatically stage it when we read it, but
2880+
# that could take a very long time (varying from minutes to days).
2881+
# All that time, the curl would be waiting for the file,
2882+
# keeping a transfer slot occupied, and confusing the user.
2883+
# So, we refuse to download tape files, and suggest to stage them.
2884+
if ! is_online "$path" ; then
2885+
echo 1>&2 "ERROR: file '$path' exists, but it is on tape, and not online." \
2886+
"You need to stage it first before you can download it." \
2887+
"You can use 'ada --stage $path' for this."
2888+
exit 1
2889+
fi
2890+
# We can't download through the API; we need a WebDAV door for that.
2891+
# If the user hasn't prepended the WebDAV door to the source,
2892+
# we need to discover it.
2893+
if [[ -z $webdav_door ]] ; then
2894+
webdav_door=$(get_webdav_door "$api")
2895+
else
2896+
$debug && echo 1>&2 "User specified WebDAV door: $webdav_door"
2897+
fi
2898+
# We should be ready to download!
2899+
$debug && set -x # If --debug is specified, show curl command (tracing on)
2900+
curl "${curl_authorization[@]}" \
2901+
--location --post302 \
2902+
"$webdav_door$source" \
2903+
"${curl_target[@]}"
2904+
{ set +x ; } 2>/dev/null # Silently switch off tracing
2905+
if $verify_checksum ; then
2906+
# For now, we just print the checksums; verify will be implemented later.
2907+
remote_checksum=$(get_checksum ADLER32 "$path")
2908+
if [[ -n $remote_checksum ]] ; then
2909+
local_checksum=$(get_local_checksum ADLER32 "$real_target")
2910+
else
2911+
remote_checksum=$(get_checksum MD5 "$path")
2912+
if [[ -z $remote_checksum ]] ; then
2913+
echo 1>&2 "ERROR: unable to get a checksum for file '$path'."
2914+
exit 1
2915+
fi
2916+
local_checksum=$(get_local_checksum MD5 "$real_target")
2917+
fi
2918+
if [[ "$local_checksum" == "$remote_checksum" ]] ; then
2919+
if $debug ; then
2920+
echo 1>&2 "Remote checksum: $remote_checksum" ;
2921+
echo 1>&2 "Local checksum : $local_checksum" ;
2922+
fi
2923+
echo "Checksum OK."
2924+
else
2925+
{
2926+
echo "ERROR: Checksum incorrect! File '$real_target' might be damaged."
2927+
echo "Remote checksum: $remote_checksum" ;
2928+
echo "Local checksum : $local_checksum" ;
2929+
} 1>&2
2930+
fi
2931+
fi
27752932
;;
27762933
setlabel | lslabel | rmlabel )
27772934
case $(pathtype "$path") in

0 commit comments

Comments
 (0)