1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
|
#!/bin/bash
# emacs: -*- mode: python; tab-width: 4; indent-tabs-mode: t -*-
# ex: set sts=4 ts=4 sw=4 noet:
#
# 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-2022 Daniel Dent
# 2022 git-annex-remote-rclone contributors
#
# 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 '[:upper:]' '[:lower:]')
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
}
printcmdresult() {
cmd="$1"
rc="$2"
out="$3"
# Replace explicit newline since we must provide 1 line DEBUG
# Fancy sed is from Example 5 of https://linuxhint.com/newline_replace_sed
# which worked on Linux and OSX.
# shellcheck disable=SC2016
out_safe=$(echo "$out" | sed -ne 'H;${x;s/\n/\\n/g;s/^,//;p;}')
echo "DEBUG '$cmd' exited with rc=$rc and stdout=${out_safe}"
}
GREP () {
set +e
out=$(grep "$@")
rc=$?
set -e
printcmdresult "grep \"$*\"" "$rc" "$out"
return $rc
}
do_checkpresent() {
key="$1"
dest="$2"
res=0
check_result=$(rclone size --json "$dest" 2>/dev/null) || res=$?
printcmdresult "rclone size --json \"$dest\"" "$res" "$check_result"
count=$(echo "$check_result" | sed -En 's/^.*"count":\s*([0-9]+).*$/\1/ p')
if [[ $res -eq 0 && "$count" -ge 1 ]]; then
# Any nonzero object count means present.
# Some rclone backends support multiple
# files under one name.
echo CHECKPRESENT-SUCCESS "$key"
elif [[ $res -eq 0 && -n "$count" && "$count" -eq 0 ]]; then
# A 0 object count means an empty directory.
echo CHECKPRESENT-FAILURE "$key"
elif [[ $res -eq 3 || $res -eq 4 ]]; then
# file or directory doesn't exist
# see https://rclone.org/docs/#exit-code
echo CHECKPRESENT-FAILURE "$key"
else
echo CHECKPRESENT-UNKNOWN "$key" "remote currently unavailable or git-annex-remote-rclone failed to parse rclone output"
fi
}
do_remove() {
key="$1"
dest="$2"
# Note that it's not a failure to remove a
# key that is not present.
if remove_result=$(rclone delete --retries 1 "$dest" 2>&1); then
echo REMOVE-SUCCESS "$key"
else
if echo "$remove_result" | GREP ' directory not found'; then
echo REMOVE-SUCCESS "$key"
else
echo REMOVE-FAILURE "$key"
fi
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'}"
# shellcheck disable=SC2086
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)
op="$2"
key="$3"
shift 3
file="$*"
case "$op" 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"
do_checkpresent "$key" "$LOC$key"
;;
REMOVE)
key="$2"
calclocation "$key"
do_remove "$key" "$LOC$key"
;;
*)
# 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
|