@@ -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+
17401794get_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