aspera-cli 4.19.0 → 4.21.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 +0 -0
- data/CHANGELOG.md +46 -0
- data/CONTRIBUTING.md +18 -4
- data/README.md +886 -510
- data/bin/asession +27 -20
- data/examples/build_exec +65 -76
- data/examples/build_exec_rubyc +40 -0
- data/examples/get_proto_file.rb +7 -0
- data/lib/aspera/agent/alpha.rb +18 -24
- data/lib/aspera/agent/base.rb +2 -18
- data/lib/aspera/agent/connect.rb +34 -15
- data/lib/aspera/agent/direct.rb +44 -54
- data/lib/aspera/agent/httpgw.rb +2 -3
- data/lib/aspera/agent/node.rb +11 -21
- data/lib/aspera/agent/{trsdk.rb → transferd.rb} +27 -51
- data/lib/aspera/api/alee.rb +15 -0
- data/lib/aspera/api/aoc.rb +139 -105
- data/lib/aspera/api/ats.rb +1 -1
- data/lib/aspera/api/cos_node.rb +1 -1
- data/lib/aspera/api/httpgw.rb +15 -10
- data/lib/aspera/api/node.rb +70 -32
- data/lib/aspera/ascmd.rb +56 -48
- data/lib/aspera/ascp/installation.rb +166 -70
- data/lib/aspera/ascp/management.rb +30 -8
- data/lib/aspera/assert.rb +10 -5
- data/lib/aspera/cli/formatter.rb +166 -162
- data/lib/aspera/cli/hints.rb +2 -1
- data/lib/aspera/cli/info.rb +12 -10
- data/lib/aspera/cli/main.rb +28 -13
- data/lib/aspera/cli/manager.rb +7 -2
- data/lib/aspera/cli/plugin.rb +17 -31
- data/lib/aspera/cli/plugins/alee.rb +3 -3
- data/lib/aspera/cli/plugins/aoc.rb +246 -208
- data/lib/aspera/cli/plugins/ats.rb +16 -14
- data/lib/aspera/cli/plugins/config.rb +154 -94
- data/lib/aspera/cli/plugins/console.rb +3 -3
- data/lib/aspera/cli/plugins/cos.rb +1 -0
- data/lib/aspera/cli/plugins/faspex.rb +15 -23
- data/lib/aspera/cli/plugins/faspex5.rb +64 -50
- data/lib/aspera/cli/plugins/faspio.rb +2 -2
- data/lib/aspera/cli/plugins/httpgw.rb +1 -1
- data/lib/aspera/cli/plugins/node.rb +174 -109
- data/lib/aspera/cli/plugins/orchestrator.rb +14 -13
- data/lib/aspera/cli/plugins/preview.rb +8 -9
- data/lib/aspera/cli/plugins/server.rb +5 -9
- data/lib/aspera/cli/plugins/shares.rb +2 -2
- data/lib/aspera/cli/sync_actions.rb +2 -2
- data/lib/aspera/cli/transfer_agent.rb +12 -14
- data/lib/aspera/cli/transfer_progress.rb +37 -17
- data/lib/aspera/cli/version.rb +1 -1
- data/lib/aspera/command_line_builder.rb +4 -5
- data/lib/aspera/coverage.rb +13 -1
- data/lib/aspera/environment.rb +75 -25
- data/lib/aspera/faspex_gw.rb +2 -2
- data/lib/aspera/json_rpc.rb +1 -1
- data/lib/aspera/keychain/macos_security.rb +7 -12
- data/lib/aspera/log.rb +3 -4
- data/lib/aspera/node_simulator.rb +230 -112
- data/lib/aspera/oauth/base.rb +64 -83
- data/lib/aspera/oauth/factory.rb +52 -6
- data/lib/aspera/oauth/generic.rb +4 -8
- data/lib/aspera/oauth/jwt.rb +6 -3
- data/lib/aspera/oauth/url_json.rb +1 -2
- data/lib/aspera/oauth/web.rb +5 -2
- data/lib/aspera/persistency_action_once.rb +16 -8
- data/lib/aspera/persistency_folder.rb +20 -2
- data/lib/aspera/preview/generator.rb +1 -1
- data/lib/aspera/preview/utils.rb +11 -17
- data/lib/aspera/products/alpha.rb +30 -0
- data/lib/aspera/products/connect.rb +48 -0
- data/lib/aspera/products/other.rb +82 -0
- data/lib/aspera/products/transferd.rb +54 -0
- data/lib/aspera/rest.rb +116 -87
- data/lib/aspera/secret_hider.rb +2 -2
- data/lib/aspera/ssh.rb +31 -24
- data/lib/aspera/transfer/faux_file.rb +4 -4
- data/lib/aspera/transfer/parameters.rb +16 -17
- data/lib/aspera/transfer/spec.rb +12 -12
- data/lib/aspera/transfer/spec.yaml +22 -20
- data/lib/aspera/transfer/sync.rb +2 -10
- data/lib/aspera/transfer/uri.rb +3 -3
- data/lib/aspera/uri_reader.rb +1 -1
- data/lib/aspera/web_auth.rb +166 -17
- data/lib/aspera/web_server_simple.rb +4 -3
- data/lib/transferd_pb.rb +86 -0
- data/lib/transferd_services_pb.rb +84 -0
- data.tar.gz.sig +0 -0
- metadata +58 -22
- metadata.gz.sig +0 -0
- data/lib/aspera/ascp/products.rb +0 -156
data/lib/aspera/api/node.rb
CHANGED
@@ -16,8 +16,6 @@ module Aspera
|
|
16
16
|
class Node < Aspera::Rest
|
17
17
|
SCOPE_SEPARATOR = ':'
|
18
18
|
SCOPE_NODE_PREFIX = 'node.'
|
19
|
-
# prefix for ruby code for filter (deprecated)
|
20
|
-
MATCH_EXEC_PREFIX = 'exec:'
|
21
19
|
MATCH_TYPES = [String, Proc, Regexp, NilClass].freeze
|
22
20
|
SIGNATURE_DELIMITER = '==SIGNATURE=='
|
23
21
|
BEARER_TOKEN_VALIDITY_DEFAULT = 86400
|
@@ -25,7 +23,7 @@ module Aspera
|
|
25
23
|
REQUIRED_APP_INFO_FIELDS = %i[api app node_info workspace_id workspace_name].freeze
|
26
24
|
# methods of @app_info[:api]
|
27
25
|
REQUIRED_APP_API_METHODS = %i[node_api_from add_ts_tags].freeze
|
28
|
-
private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :
|
26
|
+
private_constant :SCOPE_SEPARATOR, :SCOPE_NODE_PREFIX, :MATCH_TYPES,
|
29
27
|
:SIGNATURE_DELIMITER, :BEARER_TOKEN_VALIDITY_DEFAULT,
|
30
28
|
:REQUIRED_APP_INFO_FIELDS, :REQUIRED_APP_API_METHODS
|
31
29
|
|
@@ -33,6 +31,8 @@ module Aspera
|
|
33
31
|
ACCESS_LEVELS = %w[delete list mkdir preview read rename write].freeze
|
34
32
|
HEADER_X_ASPERA_ACCESS_KEY = 'X-Aspera-AccessKey'
|
35
33
|
HEADER_X_TOTAL_COUNT = 'X-Total-Count'
|
34
|
+
HEADER_X_CACHE_CONTROL = 'X-Aspera-Cache-Control'
|
35
|
+
HEADER_X_NEXT_ITER_TOKEN = 'X-Aspera-Next-Iteration-Token'
|
36
36
|
SCOPE_USER = 'user:all'
|
37
37
|
SCOPE_ADMIN = 'admin:all'
|
38
38
|
PATH_SEPARATOR = '/'
|
@@ -42,22 +42,23 @@ module Aspera
|
|
42
42
|
|
43
43
|
# class instance variable, access with accessors on class
|
44
44
|
@use_standard_ports = true
|
45
|
+
@use_node_cache = true
|
45
46
|
|
46
47
|
class << self
|
48
|
+
# set to false to read transfer parameters from download_setup
|
47
49
|
attr_accessor :use_standard_ports
|
50
|
+
# set to false to bypass cache in redis
|
51
|
+
attr_accessor :use_node_cache
|
48
52
|
|
49
53
|
# For access keys: provide expression to match entry in folder
|
54
|
+
# @param match_expression one of supported types
|
55
|
+
# @return lambda function
|
50
56
|
def file_matcher(match_expression)
|
51
57
|
case match_expression
|
52
58
|
when Proc then return match_expression
|
53
59
|
when Regexp then return ->(f){f['name'].match?(match_expression)}
|
54
60
|
when String
|
55
|
-
|
56
|
-
code = "->(f){#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}"
|
57
|
-
Log.log.warn{"Use of prefix #{MATCH_EXEC_PREFIX} is deprecated (4.15), instead use: @ruby:'#{code}'"}
|
58
|
-
return Environment.secure_eval(code, __FILE__, __LINE__)
|
59
|
-
end
|
60
|
-
return lambda{|f|File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
61
|
+
return ->(f){File.fnmatch(match_expression, f['name'], File::FNM_DOTMATCH)}
|
61
62
|
when NilClass then return ->(_){true}
|
62
63
|
else Aspera.error_unexpected_value(match_expression.class.name, exception_class: Cli::BadArgument)
|
63
64
|
end
|
@@ -146,6 +147,17 @@ module Aspera
|
|
146
147
|
end
|
147
148
|
end
|
148
149
|
|
150
|
+
# Call node API, possibly adding cache control header, as globally specified
|
151
|
+
def read_with_cache(subpath, query=nil)
|
152
|
+
headers = {'Accept' => 'application/json'}
|
153
|
+
headers[HEADER_X_CACHE_CONTROL] = 'no-cache' unless self.class.use_node_cache
|
154
|
+
return call(
|
155
|
+
operation: 'GET',
|
156
|
+
subpath: subpath,
|
157
|
+
headers: headers,
|
158
|
+
query: query)[:data]
|
159
|
+
end
|
160
|
+
|
149
161
|
# update transfer spec with special additional tags
|
150
162
|
def add_tspec_info(tspec)
|
151
163
|
tspec.deep_merge!(@add_tspec) unless @add_tspec.nil?
|
@@ -171,7 +183,7 @@ module Aspera
|
|
171
183
|
def entry_has_link_information(entry)
|
172
184
|
# if target information is missing in folder, try to get it on entry
|
173
185
|
if entry['target_node_id'].nil? || entry['target_id'].nil?
|
174
|
-
link_entry = read("files/#{entry['id']}")
|
186
|
+
link_entry = read("files/#{entry['id']}")
|
175
187
|
entry['target_node_id'] = link_entry['target_node_id']
|
176
188
|
entry['target_id'] = link_entry['target_id']
|
177
189
|
end
|
@@ -183,11 +195,11 @@ module Aspera
|
|
183
195
|
# Recursively browse in a folder (with non-recursive method)
|
184
196
|
# sub folders are processed if the processing method returns true
|
185
197
|
# links are processed on the respective node
|
198
|
+
# @param method_sym [Symbol] processing method, arguments: entry, path, state
|
186
199
|
# @param state [Object] state object sent to processing method
|
187
200
|
# @param top_file_id [String] file id to start at (default = access key root file id)
|
188
201
|
# @param top_file_path [String] path of top folder (default = /)
|
189
|
-
|
190
|
-
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/')
|
202
|
+
def process_folder_tree(method_sym:, state:, top_file_id:, top_file_path: '/', query: nil)
|
191
203
|
Aspera.assert(!top_file_path.nil?){'top_file_path not set'}
|
192
204
|
Log.log.debug{"process_folder_tree: node=#{@app_info ? @app_info[:node_info]['id'] : 'nil'}, file id=#{top_file_id}, path=#{top_file_path}"}
|
193
205
|
# start at top folder
|
@@ -200,44 +212,51 @@ module Aspera
|
|
200
212
|
# get folder content
|
201
213
|
folder_contents =
|
202
214
|
begin
|
203
|
-
|
215
|
+
# TODO: use header
|
216
|
+
read_with_cache("files/#{current_item[:id]}/files")
|
204
217
|
rescue StandardError => e
|
205
218
|
Log.log.warn{"#{current_item[:path]}: #{e.class} #{e.message}"}
|
206
219
|
[]
|
207
220
|
end
|
208
221
|
Log.log.debug{Log.dump(:folder_contents, folder_contents)}
|
209
222
|
folder_contents.each do |entry|
|
210
|
-
|
211
|
-
|
223
|
+
if entry.key?('error')
|
224
|
+
if entry['error'].is_a?(Hash) && entry['error'].key?('user_message')
|
225
|
+
Log.log.error(entry['error']['user_message'])
|
226
|
+
end
|
227
|
+
next
|
228
|
+
end
|
229
|
+
current_path = File.join(current_item[:path], entry['name'])
|
230
|
+
Log.log.debug{"process_folder_tree: checking #{current_path}"}
|
212
231
|
# call block, continue only if method returns true
|
213
|
-
next unless send(method_sym, entry,
|
232
|
+
next unless send(method_sym, entry, current_path, state)
|
214
233
|
# entry type is file, folder or link
|
215
234
|
case entry['type']
|
216
235
|
when 'folder'
|
217
|
-
folders_to_explore.push({id: entry['id'], path:
|
236
|
+
folders_to_explore.push({id: entry['id'], path: current_path})
|
218
237
|
when 'link'
|
219
238
|
if entry_has_link_information(entry)
|
220
239
|
node_id_to_node(entry['target_node_id'])&.process_folder_tree(
|
221
240
|
method_sym: method_sym,
|
222
241
|
state: state,
|
223
242
|
top_file_id: entry['target_id'],
|
224
|
-
top_file_path:
|
243
|
+
top_file_path: current_path)
|
225
244
|
end
|
226
245
|
end
|
227
246
|
end
|
228
247
|
end
|
229
248
|
end
|
230
249
|
|
231
|
-
# Navigate the path from given file id
|
250
|
+
# Navigate the path from given file id on current node, and return the node and file id of target.
|
251
|
+
# If the path ends with a "/" or process_last_link is true then if the last item in path is a link, it is followed.
|
232
252
|
# @param top_file_id [String] id initial file id
|
233
|
-
# @param path [String]
|
253
|
+
# @param path [String] file or folder path (end with "/" is like setting process_last_link)
|
254
|
+
# @param process_last_link [Boolean] if true, follow the last link
|
234
255
|
# @return [Hash] {.api,.file_id}
|
235
|
-
def resolve_api_fid(top_file_id, path)
|
256
|
+
def resolve_api_fid(top_file_id, path, process_last_link=false)
|
236
257
|
Aspera.assert_type(top_file_id, String)
|
237
258
|
Aspera.assert_type(path, String)
|
238
|
-
|
239
|
-
process_last_link = path.end_with?(PATH_SEPARATOR)
|
240
|
-
# keep only non-empty elements
|
259
|
+
process_last_link ||= path.end_with?(PATH_SEPARATOR)
|
241
260
|
path_elements = path.split(PATH_SEPARATOR).reject(&:empty?)
|
242
261
|
return {api: self, file_id: top_file_id} if path_elements.empty?
|
243
262
|
resolve_state = {path: path_elements, result: nil, process_last_link: process_last_link}
|
@@ -247,13 +266,19 @@ module Aspera
|
|
247
266
|
return resolve_state[:result]
|
248
267
|
end
|
249
268
|
|
250
|
-
def find_files(top_file_id,
|
269
|
+
def find_files(top_file_id, test_lambda)
|
251
270
|
Log.log.debug{"find_files: file id=#{top_file_id}"}
|
252
|
-
find_state = {found: [],
|
271
|
+
find_state = {found: [], test_lambda: test_lambda}
|
253
272
|
process_folder_tree(method_sym: :process_find_files, state: find_state, top_file_id: top_file_id)
|
254
273
|
return find_state[:found]
|
255
274
|
end
|
256
275
|
|
276
|
+
def list_files(top_file_id)
|
277
|
+
find_state = {found: []}
|
278
|
+
process_folder_tree(method_sym: :process_list_files, state: find_state, top_file_id: top_file_id)
|
279
|
+
return find_state[:found]
|
280
|
+
end
|
281
|
+
|
257
282
|
def refreshed_transfer_token
|
258
283
|
return oauth.token(refresh: true)
|
259
284
|
end
|
@@ -265,7 +290,7 @@ module Aspera
|
|
265
290
|
full_spec = create(
|
266
291
|
'files/download_setup',
|
267
292
|
{transfer_requests: [{transfer_request: {paths: [{source: '/'}]}}]}
|
268
|
-
)[
|
293
|
+
)['transfer_specs'].first['transfer_spec']
|
269
294
|
# set available fields
|
270
295
|
@std_t_spec_cache = Transfer::Spec::TRANSPORT_FIELDS.each_with_object({}) do |i, h|
|
271
296
|
h[i] = full_spec[i] if full_spec.key?(i)
|
@@ -275,6 +300,9 @@ module Aspera
|
|
275
300
|
end
|
276
301
|
|
277
302
|
# Create transfer spec for gen4
|
303
|
+
# @param file_id destination or source folder (id)
|
304
|
+
# @param direction one of Transfer::Spec::DIRECTION_SEND, Transfer::Spec::DIRECTION_RECEIVE
|
305
|
+
# @param ts_merge additional transfer spec to merge
|
278
306
|
def transfer_spec_gen4(file_id, direction, ts_merge=nil)
|
279
307
|
ak_name = nil
|
280
308
|
ak_token = nil
|
@@ -288,6 +316,9 @@ module Aspera
|
|
288
316
|
# TODO: token_generation_lambda = lambda{|do_refresh|oauth.token(refresh: do_refresh)}
|
289
317
|
# get bearer token, possibly use cache
|
290
318
|
ak_token = oauth.token
|
319
|
+
when :none
|
320
|
+
ak_name = params[:headers][HEADER_X_ASPERA_ACCESS_KEY]
|
321
|
+
ak_token = params[:headers]['Authorization']
|
291
322
|
else Aspera.error_unexpected_value(auth_params[:type])
|
292
323
|
end
|
293
324
|
transfer_spec = {
|
@@ -317,13 +348,13 @@ module Aspera
|
|
317
348
|
if !@app_info.nil? && !@app_info[:node_info]['transfer_url'].nil? && !@app_info[:node_info]['transfer_url'].empty?
|
318
349
|
transfer_spec['remote_host'] = @app_info[:node_info]['transfer_url']
|
319
350
|
end
|
320
|
-
info = read('info')
|
351
|
+
info = read('info')
|
321
352
|
# get the transfer user from info on access key
|
322
353
|
transfer_spec['remote_user'] = info['transfer_user'] if info['transfer_user']
|
323
354
|
# get settings from name.value array to hash key.value
|
324
355
|
settings = info['settings']&.each_with_object({}){|i, h|h[i['name']] = i['value']}
|
325
356
|
# check WSS ports
|
326
|
-
|
357
|
+
Transfer::Spec::WSS_FIELDS.each do |i|
|
327
358
|
transfer_spec[i] = settings[i] if settings.key?(i)
|
328
359
|
end if settings.is_a?(Hash)
|
329
360
|
else
|
@@ -336,8 +367,8 @@ module Aspera
|
|
336
367
|
|
337
368
|
private
|
338
369
|
|
370
|
+
# method called in loop for each entry for `resolve_api_fid`
|
339
371
|
def process_api_fid(entry, path, state)
|
340
|
-
# this block is called recursively for each entry in folder
|
341
372
|
# stop digging here if not in right path
|
342
373
|
return false unless entry['name'].eql?(state[:path].first)
|
343
374
|
# ok it matches, so we remove the match, and continue digging
|
@@ -380,11 +411,18 @@ module Aspera
|
|
380
411
|
return true
|
381
412
|
end
|
382
413
|
|
383
|
-
|
384
|
-
|
414
|
+
# method called in loop for each entry for `find_files`
|
415
|
+
def process_find_files(entry, path, state)
|
416
|
+
state[:found].push(entry.merge({'path' => path})) if state[:test_lambda].call(entry)
|
385
417
|
# test all files deeply
|
386
418
|
return true
|
387
419
|
end
|
420
|
+
|
421
|
+
# method called in loop for each entry for `list_files`
|
422
|
+
def process_list_files(entry, path, state)
|
423
|
+
state[:found].push(entry.merge({'path' => path}))
|
424
|
+
return false
|
425
|
+
end
|
388
426
|
end
|
389
427
|
end
|
390
428
|
end
|
data/lib/aspera/ascmd.rb
CHANGED
@@ -22,7 +22,54 @@ module Aspera
|
|
22
22
|
mv: 2,
|
23
23
|
rm: 1
|
24
24
|
}.freeze
|
25
|
-
|
25
|
+
|
26
|
+
# protocol is based on Type-Length-Value
|
27
|
+
# type start at one, but array index start at zero
|
28
|
+
ENUM_START = 1
|
29
|
+
|
30
|
+
# description of result structures (see ascmdtypes.h).
|
31
|
+
# Base types are big endian
|
32
|
+
# key = name of type
|
33
|
+
# index in array `fields` is the type (minus ENUM_START)
|
34
|
+
# decoding always start at `result`
|
35
|
+
# some fields have special handling indicated by `special`
|
36
|
+
# field_list, list_tlv_list, list_tlv_restart are composed with a list of TLV
|
37
|
+
TYPES_DESCR = {
|
38
|
+
result: {decode: :field_list,
|
39
|
+
fields: [{name: :file, is_a: :stat}, {name: :dir, is_a: :stat, special: :list_tlv_list}, {name: :size, is_a: :size}, {name: :error, is_a: :error},
|
40
|
+
{name: :info, is_a: :info}, {name: :success, is_a: nil, special: :return_true}, {name: :exit, is_a: nil},
|
41
|
+
{name: :df, is_a: :mnt, special: :list_tlv_restart}, {name: :md5sum, is_a: :md5sum}]},
|
42
|
+
stat: {decode: :field_list,
|
43
|
+
fields: [{name: :name, is_a: :zstr}, {name: :size, is_a: :int64}, {name: :mode, is_a: :int32, check: nil}, {name: :zmode, is_a: :zstr},
|
44
|
+
{name: :uid, is_a: :int32, check: nil}, {name: :zuid, is_a: :zstr}, {name: :gid, is_a: :int32, check: nil}, {name: :zgid, is_a: :zstr},
|
45
|
+
{name: :ctime, is_a: :epoch}, {name: :zctime, is_a: :zstr}, {name: :mtime, is_a: :epoch}, {name: :zmtime, is_a: :zstr},
|
46
|
+
{name: :atime, is_a: :epoch}, {name: :zatime, is_a: :zstr}, {name: :symlink, is_a: :zstr}, {name: :errno, is_a: :int32},
|
47
|
+
{name: :errstr, is_a: :zstr}]},
|
48
|
+
info: {decode: :field_list,
|
49
|
+
fields: [{name: :platform, is_a: :zstr}, {name: :version, is_a: :zstr}, {name: :lang, is_a: :zstr}, {name: :territory, is_a: :zstr},
|
50
|
+
{name: :codeset, is_a: :zstr}, {name: :lc_ctype, is_a: :zstr}, {name: :lc_numeric, is_a: :zstr}, {name: :lc_time, is_a: :zstr},
|
51
|
+
{name: :lc_all, is_a: :zstr}, {name: :dev, is_a: :zstr, special: :list_multiple}, {name: :browse_caps, is_a: :zstr},
|
52
|
+
{name: :protocol, is_a: :zstr}]},
|
53
|
+
size: {decode: :field_list,
|
54
|
+
fields: [{name: :size, is_a: :int64}, {name: :fcount, is_a: :int32}, {name: :dcount, is_a: :int32}, {name: :failed_fcount, is_a: :int32},
|
55
|
+
{name: :failed_dcount, is_a: :int32}]},
|
56
|
+
error: {decode: :field_list,
|
57
|
+
fields: [{name: :errno, is_a: :int32}, {name: :errstr, is_a: :zstr}]},
|
58
|
+
mnt: {decode: :field_list,
|
59
|
+
fields: [{name: :fs, is_a: :zstr}, {name: :dir, is_a: :zstr}, {name: :is_a, is_a: :zstr}, {name: :total, is_a: :int64},
|
60
|
+
{name: :used, is_a: :int64}, {name: :free, is_a: :int64}, {name: :fcount, is_a: :int64}, {name: :errno, is_a: :int32},
|
61
|
+
{name: :errstr, is_a: :zstr}]},
|
62
|
+
md5sum: {decode: :field_list, fields: [{name: :md5sum, is_a: :zstr}]},
|
63
|
+
int8: {decode: :base, unpack: 'C', size: 1},
|
64
|
+
int32: {decode: :base, unpack: 'L>', size: 4},
|
65
|
+
int64: {decode: :base, unpack: 'Q>', size: 8},
|
66
|
+
epoch: {decode: :base, unpack: 'Q>', size: 8},
|
67
|
+
zstr: {decode: :base, unpack: 'Z*'},
|
68
|
+
blist: {decode: :buffer_list}
|
69
|
+
}.freeze
|
70
|
+
|
71
|
+
private_constant :TYPES_DESCR, :ENUM_START, :OPS_ARGS
|
72
|
+
|
26
73
|
# list of supported actions
|
27
74
|
OPERATIONS = OPS_ARGS.keys.freeze
|
28
75
|
|
@@ -55,6 +102,7 @@ module Aspera
|
|
55
102
|
arg_batches.each do |args|
|
56
103
|
command = [main_command]
|
57
104
|
# enclose arguments in double quotes, protect backslash and double quotes
|
105
|
+
# ascmd uses space as token separator, and optional quotes ('") or \ to escape
|
58
106
|
args.each do |v|
|
59
107
|
command.push(%Q{"#{v.gsub(/["\\]/){|s|"\\#{s}"}}"})
|
60
108
|
end
|
@@ -106,47 +154,6 @@ module Aspera
|
|
106
154
|
def extended_message; "ascmd: errno=#{@errno} errstr=\"#{@errstr}\" command=#{@command} arguments=#{@arguments&.join(',')}"; end
|
107
155
|
end
|
108
156
|
|
109
|
-
# description of result structures (see ascmdtypes.h). Base types are big endian
|
110
|
-
# key = name of type
|
111
|
-
TYPES_DESCR = {
|
112
|
-
result: {decode: :field_list,
|
113
|
-
fields: [{name: :file, is_a: :stat}, {name: :dir, is_a: :stat, special: :sub_struct}, {name: :size, is_a: :size}, {name: :error, is_a: :error},
|
114
|
-
{name: :info, is_a: :info}, {name: :success, is_a: nil, special: :return_true}, {name: :exit, is_a: nil},
|
115
|
-
{name: :df, is_a: :mnt, special: :restart_on_first}, {name: :md5sum, is_a: :md5sum}]},
|
116
|
-
stat: {decode: :field_list,
|
117
|
-
fields: [{name: :name, is_a: :zstr}, {name: :size, is_a: :int64}, {name: :mode, is_a: :int32, check: nil}, {name: :zmode, is_a: :zstr},
|
118
|
-
{name: :uid, is_a: :int32, check: nil}, {name: :zuid, is_a: :zstr}, {name: :gid, is_a: :int32, check: nil}, {name: :zgid, is_a: :zstr},
|
119
|
-
{name: :ctime, is_a: :epoch}, {name: :zctime, is_a: :zstr}, {name: :mtime, is_a: :epoch}, {name: :zmtime, is_a: :zstr},
|
120
|
-
{name: :atime, is_a: :epoch}, {name: :zatime, is_a: :zstr}, {name: :symlink, is_a: :zstr}, {name: :errno, is_a: :int32},
|
121
|
-
{name: :errstr, is_a: :zstr}]},
|
122
|
-
info: {decode: :field_list,
|
123
|
-
fields: [{name: :platform, is_a: :zstr}, {name: :version, is_a: :zstr}, {name: :lang, is_a: :zstr}, {name: :territory, is_a: :zstr},
|
124
|
-
{name: :codeset, is_a: :zstr}, {name: :lc_ctype, is_a: :zstr}, {name: :lc_numeric, is_a: :zstr}, {name: :lc_time, is_a: :zstr},
|
125
|
-
{name: :lc_all, is_a: :zstr}, {name: :dev, is_a: :zstr, special: :multiple}, {name: :browse_caps, is_a: :zstr},
|
126
|
-
{name: :protocol, is_a: :zstr}]},
|
127
|
-
size: {decode: :field_list,
|
128
|
-
fields: [{name: :size, is_a: :int64}, {name: :fcount, is_a: :int32}, {name: :dcount, is_a: :int32}, {name: :failed_fcount, is_a: :int32},
|
129
|
-
{name: :failed_dcount, is_a: :int32}]},
|
130
|
-
error: {decode: :field_list,
|
131
|
-
fields: [{name: :errno, is_a: :int32}, {name: :errstr, is_a: :zstr}]},
|
132
|
-
mnt: {decode: :field_list,
|
133
|
-
fields: [{name: :fs, is_a: :zstr}, {name: :dir, is_a: :zstr}, {name: :is_a, is_a: :zstr}, {name: :total, is_a: :int64},
|
134
|
-
{name: :used, is_a: :int64}, {name: :free, is_a: :int64}, {name: :fcount, is_a: :int64}, {name: :errno, is_a: :int32},
|
135
|
-
{name: :errstr, is_a: :zstr}]},
|
136
|
-
md5sum: {decode: :field_list, fields: [{name: :md5sum, is_a: :zstr}]},
|
137
|
-
int8: {decode: :base, unpack: 'C', size: 1},
|
138
|
-
int32: {decode: :base, unpack: 'L>', size: 4},
|
139
|
-
int64: {decode: :base, unpack: 'Q>', size: 8},
|
140
|
-
epoch: {decode: :base, unpack: 'Q>', size: 8},
|
141
|
-
zstr: {decode: :base, unpack: 'Z*'},
|
142
|
-
blist: {decode: :buffer_list}
|
143
|
-
}.freeze
|
144
|
-
|
145
|
-
# protocol enum start at one, but array index start at zero
|
146
|
-
ENUM_START = 1
|
147
|
-
|
148
|
-
private_constant :TYPES_DESCR, :ENUM_START
|
149
|
-
|
150
157
|
class << self
|
151
158
|
# get description of structure's field, @param struct_name, @param typed_buffer provides field name
|
152
159
|
def field_description(struct_name, typed_buffer)
|
@@ -175,6 +182,7 @@ module Aspera
|
|
175
182
|
Log.log.trace1{"#{' .' * indent_level}-> base:#{byte_array} -> #{result}"}
|
176
183
|
result = Time.at(result) if type_name.eql?(:epoch)
|
177
184
|
when :buffer_list
|
185
|
+
# return a list of type_buffer
|
178
186
|
result = []
|
179
187
|
until buffer.empty?
|
180
188
|
btype = parse(buffer, :int8, indent_level)
|
@@ -193,16 +201,16 @@ module Aspera
|
|
193
201
|
field_info = field_description(type_name, typed_buffer)
|
194
202
|
Log.log.trace1{"#{' .' * indent_level}+ field(special=#{field_info[:special]})=#{field_info[:name]}".green}
|
195
203
|
case field_info[:special]
|
196
|
-
when nil
|
204
|
+
when nil # normal case
|
197
205
|
result[field_info[:name]] = parse(typed_buffer[:buffer], field_info[:is_a], indent_level)
|
198
|
-
when :return_true
|
206
|
+
when :return_true # nothing to parse, just return true
|
199
207
|
result[field_info[:name]] = true
|
200
|
-
when :
|
201
|
-
result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
|
202
|
-
when :multiple
|
208
|
+
when :list_multiple # field appears multiple times, and is an array of values (base type)
|
203
209
|
result[field_info[:name]] ||= []
|
204
210
|
result[field_info[:name]].push(parse(typed_buffer[:buffer], field_info[:is_a], indent_level))
|
205
|
-
when :
|
211
|
+
when :list_tlv_list # field is an array of values in a list of buffers
|
212
|
+
result[field_info[:name]] = parse(typed_buffer[:buffer], :blist, indent_level).map{|r|parse(r[:buffer], field_info[:is_a], indent_level)}
|
213
|
+
when :list_tlv_restart # field is an array of values, but a new value is started on index 1
|
206
214
|
fl = result[field_info[:name]] = []
|
207
215
|
parse(typed_buffer[:buffer], :blist, indent_level).map do |tb|
|
208
216
|
fl.push({}) if tb[:btype].eql?(ENUM_START)
|