aspera-cli 4.17.0 → 4.18.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +3 -4
- data/CHANGELOG.md +33 -0
- data/CONTRIBUTING.md +15 -1
- data/README.md +711 -432
- data/bin/ascli +5 -0
- data/bin/asession +2 -2
- data/examples/build_package.sh +28 -0
- data/lib/aspera/agent/alpha.rb +10 -8
- data/lib/aspera/agent/base.rb +9 -6
- data/lib/aspera/agent/connect.rb +7 -8
- data/lib/aspera/agent/direct.rb +56 -37
- data/lib/aspera/agent/httpgw.rb +23 -324
- data/lib/aspera/agent/node.rb +19 -20
- data/lib/aspera/agent/trsdk.rb +19 -20
- data/lib/aspera/api/aoc.rb +17 -14
- data/lib/aspera/api/cos_node.rb +4 -4
- data/lib/aspera/api/httpgw.rb +342 -0
- data/lib/aspera/api/node.rb +135 -89
- data/lib/aspera/ascmd.rb +4 -3
- data/lib/aspera/ascp/installation.rb +15 -7
- data/lib/aspera/ascp/management.rb +2 -2
- data/lib/aspera/ascp/products.rb +1 -1
- data/lib/aspera/cli/basic_auth_plugin.rb +5 -9
- data/lib/aspera/cli/extended_value.rb +35 -16
- data/lib/aspera/cli/formatter.rb +161 -70
- data/lib/aspera/cli/hints.rb +18 -0
- data/lib/aspera/cli/main.rb +32 -39
- data/lib/aspera/cli/manager.rb +151 -119
- data/lib/aspera/cli/plugin.rb +27 -21
- data/lib/aspera/cli/plugin_factory.rb +31 -20
- data/lib/aspera/cli/plugins/alee.rb +14 -2
- data/lib/aspera/cli/plugins/aoc.rb +152 -141
- data/lib/aspera/cli/plugins/ats.rb +1 -1
- data/lib/aspera/cli/plugins/config.rb +72 -65
- data/lib/aspera/cli/plugins/console.rb +8 -5
- data/lib/aspera/cli/plugins/faspex.rb +32 -23
- data/lib/aspera/cli/plugins/faspex5.rb +232 -156
- data/lib/aspera/cli/plugins/faspio.rb +85 -0
- data/lib/aspera/cli/plugins/httpgw.rb +55 -0
- data/lib/aspera/cli/plugins/node.rb +129 -64
- data/lib/aspera/cli/plugins/orchestrator.rb +33 -30
- data/lib/aspera/cli/plugins/preview.rb +7 -3
- data/lib/aspera/cli/plugins/server.rb +6 -6
- data/lib/aspera/cli/plugins/shares.rb +16 -14
- data/lib/aspera/cli/special_values.rb +13 -0
- data/lib/aspera/cli/sync_actions.rb +10 -10
- data/lib/aspera/cli/transfer_agent.rb +7 -6
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/environment.rb +70 -9
- data/lib/aspera/faspex_gw.rb +5 -4
- data/lib/aspera/faspex_postproc.rb +2 -2
- data/lib/aspera/log.rb +6 -3
- data/lib/aspera/node_simulator.rb +2 -2
- data/lib/aspera/oauth/base.rb +31 -19
- data/lib/aspera/oauth/factory.rb +12 -13
- data/lib/aspera/oauth/generic.rb +1 -0
- data/lib/aspera/oauth/jwt.rb +18 -15
- data/lib/aspera/oauth/url_json.rb +8 -6
- data/lib/aspera/oauth/web.rb +2 -2
- data/lib/aspera/persistency_folder.rb +2 -2
- data/lib/aspera/preview/generator.rb +3 -3
- data/lib/aspera/preview/options.rb +3 -3
- data/lib/aspera/preview/terminal.rb +4 -4
- data/lib/aspera/preview/utils.rb +3 -3
- data/lib/aspera/proxy_auto_config.rb +5 -1
- data/lib/aspera/rest.rb +105 -88
- data/lib/aspera/rest_call_error.rb +1 -1
- data/lib/aspera/rest_error_analyzer.rb +2 -2
- data/lib/aspera/rest_errors_aspera.rb +1 -1
- data/lib/aspera/resumer.rb +1 -1
- data/lib/aspera/secret_hider.rb +2 -4
- data/lib/aspera/ssh.rb +1 -1
- data/lib/aspera/transfer/parameters.rb +39 -36
- data/lib/aspera/transfer/spec.rb +2 -0
- data/lib/aspera/transfer/sync.rb +2 -1
- data/lib/aspera/transfer/uri.rb +1 -1
- data/lib/aspera/uri_reader.rb +5 -4
- data/lib/aspera/web_auth.rb +1 -1
- data/lib/aspera/web_server_simple.rb +4 -3
- data.tar.gz.sig +0 -0
- metadata +7 -4
- metadata.gz.sig +0 -0
- data/lib/aspera/cli/plugins/bss.rb +0 -71
- data/lib/aspera/open_application.rb +0 -71
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'aspera/log'
|
|
4
|
+
require 'aspera/rest'
|
|
5
|
+
require 'aspera/transfer/faux_file'
|
|
6
|
+
require 'aspera/assert'
|
|
7
|
+
require 'securerandom'
|
|
8
|
+
require 'websocket'
|
|
9
|
+
require 'base64'
|
|
10
|
+
require 'json'
|
|
11
|
+
|
|
12
|
+
module Aspera
|
|
13
|
+
module Api
|
|
14
|
+
# Start a transfer using Aspera HTTP Gateway, using web socket secure for uploads
|
|
15
|
+
# ref: https://api.ibm.com/explorer/catalog/aspera/product/ibm-aspera/api/http-gateway-api/doc/guides-toc
|
|
16
|
+
# https://developer.ibm.com/apis/catalog?search=%22aspera%20http%22
|
|
17
|
+
# HTTP GW Upload protocol:
|
|
18
|
+
# # type Contents Ack Counter
|
|
19
|
+
# v1
|
|
20
|
+
# 0 JSON.transfer_spec Transfer Spec "end upload" sent_general
|
|
21
|
+
# 1.. JSON.slice_upload File base64 chunks "end upload" sent_general
|
|
22
|
+
# v2
|
|
23
|
+
# 0 JSON.transfer_spec Transfer Spec "end upload" sent_general
|
|
24
|
+
# 1 JSON.slice_upload File start "end_slice_upload" sent_v2_delimiter
|
|
25
|
+
# 2.. Binary File binary chunks "end upload" sent_general
|
|
26
|
+
# last JSON.slice_upload File end "end_slice_upload" sent_v2_delimiter
|
|
27
|
+
class Httpgw < Aspera::Rest
|
|
28
|
+
DEFAULT_BASE_PATH = '/aspera/http-gwy'
|
|
29
|
+
INFO_ENDPOINT = 'info'
|
|
30
|
+
MSG_SEND_TRANSFER_SPEC = 'transfer_spec'
|
|
31
|
+
MSG_SEND_SLICE_UPLOAD = 'slice_upload'
|
|
32
|
+
MSG_RECV_DATA_RECEIVED_SIGNAL = 'end upload'
|
|
33
|
+
MSG_RECV_SLICE_UPLOAD_SIGNAL = 'end_slice_upload'
|
|
34
|
+
# upload API versions
|
|
35
|
+
API_V1 = 'v1'
|
|
36
|
+
API_V2 = 'v2'
|
|
37
|
+
THR_RECV = 'recv'
|
|
38
|
+
LOG_WS_SEND = 'ws: send: '.red
|
|
39
|
+
LOG_WS_RECV = "ws: #{THR_RECV}: ".green
|
|
40
|
+
private_constant :MSG_RECV_DATA_RECEIVED_SIGNAL, :MSG_RECV_SLICE_UPLOAD_SIGNAL
|
|
41
|
+
# send message on http gw web socket
|
|
42
|
+
def ws_snd_json(msg_type, payload)
|
|
43
|
+
if msg_type.eql?(MSG_SEND_SLICE_UPLOAD) && @upload_version.eql?(API_V2)
|
|
44
|
+
@shared_info[:count][:sent_v2_delimiter] += 1
|
|
45
|
+
else
|
|
46
|
+
@shared_info[:count][:sent_general] += 1
|
|
47
|
+
end
|
|
48
|
+
Log.log.debug do
|
|
49
|
+
log_data = payload.dup
|
|
50
|
+
log_data[:data] = "[data #{log_data[:data].length} bytes]" if log_data.key?(:data)
|
|
51
|
+
"#{LOG_WS_SEND}json: #{msg_type}: #{JSON.generate(log_data)}"
|
|
52
|
+
end
|
|
53
|
+
ws_send(ws_type: :text, data: JSON.generate({msg_type => payload}))
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
# send data on http gw web socket
|
|
57
|
+
def ws_send(ws_type:, data:)
|
|
58
|
+
Log.log.debug{"#{LOG_WS_SEND}sending: #{ws_type} (#{data&.length || 0} bytes)"}
|
|
59
|
+
@shared_info[:count][:sent_general] += 1 if ws_type.eql?(:binary)
|
|
60
|
+
frame_generator = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: ws_type, version: @ws_handshake.version)
|
|
61
|
+
@ws_io.write(frame_generator.to_s)
|
|
62
|
+
if @synchronous
|
|
63
|
+
@shared_info[:mutex].synchronize do
|
|
64
|
+
# if read thread exited, there will be no more updates
|
|
65
|
+
# we allow for 1 of difference else it stays blocked
|
|
66
|
+
while @ws_read_thread.alive? &&
|
|
67
|
+
@shared_info[:read_exception].nil? &&
|
|
68
|
+
(((@shared_info[:count][:sent_general] - @shared_info[:count][:received_general]) > 1) ||
|
|
69
|
+
((@shared_info[:count][:received_v2_delimiter] - @shared_info[:count][:sent_v2_delimiter]) > 1))
|
|
70
|
+
if !@shared_info[:cond_var].wait(@shared_info[:mutex], 2.0)
|
|
71
|
+
Log.log.debug{"#{LOG_WS_SEND}#{'timeout'.blue}: #{@shared_info[:count]}"}
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
raise @shared_info[:read_exception] unless @shared_info[:read_exception].nil?
|
|
77
|
+
Log.log.debug{"#{LOG_WS_SEND}counts: #{@shared_info[:count]}"}
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
# message processing for read thread
|
|
81
|
+
def process_received_message(message)
|
|
82
|
+
Log.log.debug{"#{LOG_WS_RECV}message: [#{message}] (#{message.class})"}
|
|
83
|
+
if message.eql?(MSG_RECV_DATA_RECEIVED_SIGNAL)
|
|
84
|
+
@shared_info[:mutex].synchronize do
|
|
85
|
+
@shared_info[:count][:received_general] += 1
|
|
86
|
+
@shared_info[:cond_var].signal
|
|
87
|
+
end
|
|
88
|
+
elsif message.eql?(MSG_RECV_SLICE_UPLOAD_SIGNAL)
|
|
89
|
+
@shared_info[:mutex].synchronize do
|
|
90
|
+
@shared_info[:count][:received_v2_delimiter] += 1
|
|
91
|
+
@shared_info[:cond_var].signal
|
|
92
|
+
end
|
|
93
|
+
else
|
|
94
|
+
message.chomp!
|
|
95
|
+
error_message =
|
|
96
|
+
if message.start_with?('"') && message.end_with?('"')
|
|
97
|
+
# remove double quotes : 1..-2
|
|
98
|
+
JSON.parse(Base64.strict_decode64(message.chomp[1..-2]))['message']
|
|
99
|
+
elsif message.start_with?('{') && message.end_with?('}')
|
|
100
|
+
JSON.parse(message)['message']
|
|
101
|
+
else
|
|
102
|
+
"unknown message from gateway: [#{message}]"
|
|
103
|
+
end
|
|
104
|
+
raise error_message
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
# main function of read thread
|
|
109
|
+
def process_read_thread
|
|
110
|
+
Log.log.debug{"#{LOG_WS_RECV}read thread started"}
|
|
111
|
+
frame_parser = ::WebSocket::Frame::Incoming::Client.new(version: @ws_handshake.version)
|
|
112
|
+
until @ws_io.eof?
|
|
113
|
+
begin # rubocop:disable Style/RedundantBegin
|
|
114
|
+
# ready byte by byte until frame is ready
|
|
115
|
+
# blocking read
|
|
116
|
+
byte = @ws_io.read(1)
|
|
117
|
+
Log.log.trace1{"#{LOG_WS_RECV}read: #{byte} (#{byte.class}) eof=#{@ws_io.eof?}"}
|
|
118
|
+
frame_parser << byte
|
|
119
|
+
frame_ok = frame_parser.next
|
|
120
|
+
next if frame_ok.nil?
|
|
121
|
+
process_received_message(frame_ok.data.to_s)
|
|
122
|
+
Log.log.debug{"#{LOG_WS_RECV}counts: #{@shared_info[:count]}"}
|
|
123
|
+
rescue => e
|
|
124
|
+
Log.log.debug{"#{LOG_WS_RECV}Exception: #{e}"}
|
|
125
|
+
@shared_info[:mutex].synchronize do
|
|
126
|
+
@shared_info[:read_exception] = e
|
|
127
|
+
@shared_info[:cond_var].signal
|
|
128
|
+
end
|
|
129
|
+
break
|
|
130
|
+
end
|
|
131
|
+
end
|
|
132
|
+
Log.log.debug do
|
|
133
|
+
"#{LOG_WS_RECV}exception: #{@shared_info[:read_exception]},cls=#{@shared_info[:read_exception].class})"
|
|
134
|
+
end unless @shared_info[:read_exception].nil?
|
|
135
|
+
Log.log.debug{"#{LOG_WS_RECV}read thread stopped (ws eof=#{@ws_io.eof?})"}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def upload(transfer_spec)
|
|
139
|
+
# identify this session uniquely
|
|
140
|
+
session_id = SecureRandom.uuid
|
|
141
|
+
@notify_cb&.call(session_id: nil, type: :pre_start, info: 'starting')
|
|
142
|
+
# total size of all files
|
|
143
|
+
total_bytes_to_transfer = 0
|
|
144
|
+
# we need to keep track of actual file path because transfer spec is modified to be sent in web socket
|
|
145
|
+
files_to_read = []
|
|
146
|
+
# get source root or nil
|
|
147
|
+
source_root = transfer_spec.key?('source_root') && !transfer_spec['source_root'].empty? ? transfer_spec['source_root'] : nil
|
|
148
|
+
# source root is ignored by GW, used only here
|
|
149
|
+
transfer_spec.delete('source_root')
|
|
150
|
+
# compute total size of files to upload (for progress)
|
|
151
|
+
# modify transfer spec to be suitable for GW
|
|
152
|
+
transfer_spec['paths'].each do |item|
|
|
153
|
+
# save actual file location to be able read contents later
|
|
154
|
+
file_to_add = Transfer::FauxFile.open(item['source'])
|
|
155
|
+
if file_to_add
|
|
156
|
+
item['source'] = file_to_add.path
|
|
157
|
+
item['file_size'] = file_to_add.size
|
|
158
|
+
else
|
|
159
|
+
file_to_add = item['source']
|
|
160
|
+
# add source root if needed
|
|
161
|
+
file_to_add = File.join(source_root, file_to_add) unless source_root.nil?
|
|
162
|
+
# GW expects a simple file name in 'source' but if user wants to change the name, we take it
|
|
163
|
+
item['source'] = File.basename(item['destination'].nil? ? item['source'] : item['destination'])
|
|
164
|
+
item['file_size'] = File.size(file_to_add)
|
|
165
|
+
end
|
|
166
|
+
# save so that we can actually read the file later
|
|
167
|
+
files_to_read.push(file_to_add)
|
|
168
|
+
total_bytes_to_transfer += item['file_size']
|
|
169
|
+
end
|
|
170
|
+
# TODO: check that this is available in endpoints: @api_info['endpoints']
|
|
171
|
+
upload_url = File.join(@gw_root_url, @upload_version, 'upload')
|
|
172
|
+
@notify_cb&.call(session_id: nil, type: :pre_start, info: 'connecting wss')
|
|
173
|
+
# open web socket to end point (equivalent to Net::HTTP.start)
|
|
174
|
+
http_session = Rest.start_http_session(upload_url)
|
|
175
|
+
# get the underlying socket i/o
|
|
176
|
+
@ws_io = Rest.io_http_session(http_session)
|
|
177
|
+
@ws_handshake = ::WebSocket::Handshake::Client.new(url: upload_url, headers: {})
|
|
178
|
+
@ws_io.write(@ws_handshake.to_s)
|
|
179
|
+
sleep(0.1)
|
|
180
|
+
@ws_handshake << @ws_io.readuntil("\r\n\r\n")
|
|
181
|
+
Aspera.assert(@ws_handshake.finished?){'Error in websocket handshake'}
|
|
182
|
+
Log.log.debug{"#{LOG_WS_SEND}handshake success"}
|
|
183
|
+
# start read thread after handshake
|
|
184
|
+
@ws_read_thread = Thread.new {process_read_thread}
|
|
185
|
+
@notify_cb&.call(session_id: session_id, type: :session_start)
|
|
186
|
+
@notify_cb&.call(session_id: session_id, type: :session_size, info: total_bytes_to_transfer)
|
|
187
|
+
sleep(1)
|
|
188
|
+
# data shared between main thread and read thread
|
|
189
|
+
@shared_info = {
|
|
190
|
+
read_exception: nil, # error message if any in callback
|
|
191
|
+
count: {
|
|
192
|
+
sent_general: 0,
|
|
193
|
+
received_general: 0,
|
|
194
|
+
sent_v2_delimiter: 0,
|
|
195
|
+
received_v2_delimiter: 0
|
|
196
|
+
},
|
|
197
|
+
mutex: Mutex.new,
|
|
198
|
+
cond_var: ConditionVariable.new
|
|
199
|
+
}
|
|
200
|
+
# notify progress bar
|
|
201
|
+
@notify_cb&.call(type: :session_size, session_id: session_id, info: total_bytes_to_transfer)
|
|
202
|
+
# first step send transfer spec
|
|
203
|
+
Log.log.debug{Log.dump(:ws_spec, transfer_spec)}
|
|
204
|
+
ws_snd_json(MSG_SEND_TRANSFER_SPEC, transfer_spec)
|
|
205
|
+
# current file index
|
|
206
|
+
file_index = 0
|
|
207
|
+
# aggregate size sent
|
|
208
|
+
session_sent_bytes = 0
|
|
209
|
+
# process each file
|
|
210
|
+
transfer_spec['paths'].each do |item|
|
|
211
|
+
slice_info = {
|
|
212
|
+
name: nil,
|
|
213
|
+
# TODO: get mime type?
|
|
214
|
+
type: 'application/octet-stream',
|
|
215
|
+
size: item['file_size'],
|
|
216
|
+
slice: 0, # current slice index
|
|
217
|
+
# index of last slice (i.e number of slices - 1)
|
|
218
|
+
last_slice: (item['file_size'] - 1) / @upload_chunk_size,
|
|
219
|
+
fileIndex: file_index
|
|
220
|
+
}
|
|
221
|
+
file = files_to_read[file_index]
|
|
222
|
+
if file.is_a?(Transfer::FauxFile)
|
|
223
|
+
slice_info[:name] = file.path
|
|
224
|
+
else
|
|
225
|
+
file = File.open(file)
|
|
226
|
+
slice_info[:name] = File.basename(item[item['destination'].nil? ? 'source' : 'destination'])
|
|
227
|
+
end
|
|
228
|
+
begin
|
|
229
|
+
until file.eof?
|
|
230
|
+
slice_bin_data = file.read(@upload_chunk_size)
|
|
231
|
+
# interrupt main thread if read thread failed
|
|
232
|
+
raise @shared_info[:read_exception] unless @shared_info[:read_exception].nil?
|
|
233
|
+
begin
|
|
234
|
+
if @upload_version.eql?(API_V1)
|
|
235
|
+
slice_info[:data] = Base64.strict_encode64(slice_bin_data)
|
|
236
|
+
ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info)
|
|
237
|
+
else
|
|
238
|
+
# send once, before data, at beginning
|
|
239
|
+
ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?(0)
|
|
240
|
+
ws_send(ws_type: :binary, data: slice_bin_data)
|
|
241
|
+
Log.log.debug{"#{LOG_WS_SEND}buffer: file: #{file_index}, slice: #{slice_info[:slice]}/#{slice_info[:last_slice]}"}
|
|
242
|
+
# send once, after data, at end
|
|
243
|
+
ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?(slice_info[:last_slice])
|
|
244
|
+
end
|
|
245
|
+
rescue Errno::EPIPE => e
|
|
246
|
+
raise @shared_info[:read_exception] unless @shared_info[:read_exception].nil?
|
|
247
|
+
raise e
|
|
248
|
+
rescue Net::ReadTimeout => e
|
|
249
|
+
Log.log.warn{'A timeout condition using HTTPGW may signal a permission problem on destination. Check ascp logs on httpgw.'}
|
|
250
|
+
raise e
|
|
251
|
+
end
|
|
252
|
+
session_sent_bytes += slice_bin_data.length
|
|
253
|
+
@notify_cb&.call(type: :transfer, session_id: session_id, info: session_sent_bytes)
|
|
254
|
+
slice_info[:slice] += 1
|
|
255
|
+
end
|
|
256
|
+
ensure
|
|
257
|
+
file.close
|
|
258
|
+
end
|
|
259
|
+
file_index += 1
|
|
260
|
+
end
|
|
261
|
+
# throttling may have skipped last one
|
|
262
|
+
@notify_cb&.call(type: :transfer, session_id: session_id, info: session_sent_bytes)
|
|
263
|
+
@notify_cb&.call(type: :end, session_id: session_id)
|
|
264
|
+
ws_send(ws_type: :close, data: nil)
|
|
265
|
+
Log.log.debug("Finished upload, waiting for end of #{THR_RECV} thread.")
|
|
266
|
+
@ws_read_thread.join
|
|
267
|
+
Log.log.debug{'Read thread joined'}
|
|
268
|
+
# session no more used
|
|
269
|
+
@ws_io = nil
|
|
270
|
+
http_session&.finish
|
|
271
|
+
end
|
|
272
|
+
|
|
273
|
+
def download(transfer_spec)
|
|
274
|
+
transfer_spec['zip_required'] ||= false
|
|
275
|
+
transfer_spec['source_root'] ||= '/'
|
|
276
|
+
# is normally provided by application, like package name
|
|
277
|
+
if !transfer_spec.key?('download_name')
|
|
278
|
+
# by default it is the name of first file
|
|
279
|
+
download_name = File.basename(transfer_spec['paths'].first['source'], '.*')
|
|
280
|
+
# ands add indication of number of files if there is more than one
|
|
281
|
+
if transfer_spec['paths'].length > 1
|
|
282
|
+
download_name += " #{transfer_spec['paths'].length} Files"
|
|
283
|
+
end
|
|
284
|
+
transfer_spec['download_name'] = download_name
|
|
285
|
+
end
|
|
286
|
+
creation = create('download', {'transfer_spec' => transfer_spec})[:data]
|
|
287
|
+
transfer_uuid = creation['url'].split('/').last
|
|
288
|
+
file_name =
|
|
289
|
+
if transfer_spec['zip_required'] || transfer_spec['paths'].length > 1
|
|
290
|
+
# it is a zip file if zip is required or there is more than 1 file
|
|
291
|
+
transfer_spec['download_name'] + '.zip'
|
|
292
|
+
else
|
|
293
|
+
# it is a plain file if we don't require zip and there is only one file
|
|
294
|
+
File.basename(transfer_spec['paths'].first['source'])
|
|
295
|
+
end
|
|
296
|
+
file_path = File.join(transfer_spec['destination_root'], file_name)
|
|
297
|
+
call(operation: 'GET', subpath: "download/#{transfer_uuid}", save_to_file: file_path)
|
|
298
|
+
end
|
|
299
|
+
|
|
300
|
+
def info
|
|
301
|
+
return @api_info
|
|
302
|
+
end
|
|
303
|
+
|
|
304
|
+
# @param url [String] URL of the HTTP Gateway, without version
|
|
305
|
+
def initialize(
|
|
306
|
+
url:,
|
|
307
|
+
api_version: API_V2,
|
|
308
|
+
upload_chunk_size: 64_000,
|
|
309
|
+
synchronous: false,
|
|
310
|
+
notify_cb: nil,
|
|
311
|
+
**opts
|
|
312
|
+
)
|
|
313
|
+
Log.log.debug{Log.dump(:gw_url, url)}
|
|
314
|
+
# add scheme if missing
|
|
315
|
+
url = "https://#{url}" unless url.match?(%r{^[a-z]{1,6}://})
|
|
316
|
+
raise 'GW URL shall be with scheme https' unless url.start_with?('https://')
|
|
317
|
+
# remove trailing slash and version (o=only once) if present
|
|
318
|
+
# TODO: issue warning ?
|
|
319
|
+
url = url.gsub(%r{/+$}, '').gsub(%r{/#{API_V1}$}o, '')
|
|
320
|
+
# assume GW is always under specific path (TODO: remove this ?)
|
|
321
|
+
url = File.join(url, DEFAULT_BASE_PATH) unless url.end_with?(DEFAULT_BASE_PATH)
|
|
322
|
+
@gw_root_url = url
|
|
323
|
+
super(base_url: "#{@gw_root_url}/#{API_V1}", **opts)
|
|
324
|
+
@upload_version = api_version
|
|
325
|
+
@upload_chunk_size = upload_chunk_size
|
|
326
|
+
@synchronous = synchronous
|
|
327
|
+
@notify_cb = notify_cb
|
|
328
|
+
# get API info
|
|
329
|
+
@api_info = read('info')[:data].freeze
|
|
330
|
+
Log.log.debug{Log.dump(:api_info, @api_info)}
|
|
331
|
+
# web socket endpoint: by default use v2 (newer gateways), without base64 encoding
|
|
332
|
+
# is the latest supported? else revert to old api
|
|
333
|
+
if !@upload_version.eql?(API_V1)
|
|
334
|
+
if !@api_info['endpoints'].any?{|i|i.include?(@upload_version)}
|
|
335
|
+
Log.log.warn{"API version #{@upload_version} not supported, reverting to #{API_V1}"}
|
|
336
|
+
@upload_version = API_V1
|
|
337
|
+
end
|
|
338
|
+
end
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
end
|