aspera-cli 4.25.3 → 4.26.0.pre

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.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +39 -6
  4. data/CONTRIBUTING.md +119 -111
  5. data/README.md +9 -7
  6. data/lib/aspera/agent/direct.rb +10 -8
  7. data/lib/aspera/agent/factory.rb +3 -3
  8. data/lib/aspera/agent/node.rb +1 -1
  9. data/lib/aspera/api/alee.rb +1 -0
  10. data/lib/aspera/api/aoc.rb +13 -12
  11. data/lib/aspera/api/ats.rb +1 -1
  12. data/lib/aspera/api/cos_node.rb +5 -0
  13. data/lib/aspera/api/faspex.rb +15 -2
  14. data/lib/aspera/api/httpgw.rb +2 -0
  15. data/lib/aspera/api/node.rb +82 -29
  16. data/lib/aspera/ascp/installation.rb +9 -10
  17. data/lib/aspera/cli/error.rb +8 -0
  18. data/lib/aspera/cli/formatter.rb +27 -11
  19. data/lib/aspera/cli/info.rb +2 -1
  20. data/lib/aspera/cli/main.rb +30 -12
  21. data/lib/aspera/cli/manager.rb +43 -31
  22. data/lib/aspera/cli/plugins/aoc.rb +7 -5
  23. data/lib/aspera/cli/plugins/base.rb +2 -79
  24. data/lib/aspera/cli/plugins/config.rb +2 -1
  25. data/lib/aspera/cli/plugins/faspex.rb +1 -1
  26. data/lib/aspera/cli/plugins/faspex5.rb +51 -51
  27. data/lib/aspera/cli/plugins/node.rb +9 -14
  28. data/lib/aspera/cli/plugins/shares.rb +4 -2
  29. data/lib/aspera/cli/special_values.rb +1 -0
  30. data/lib/aspera/cli/transfer_agent.rb +3 -0
  31. data/lib/aspera/cli/version.rb +1 -1
  32. data/lib/aspera/cli/wizard.rb +2 -1
  33. data/lib/aspera/dot_container.rb +10 -10
  34. data/lib/aspera/log.rb +1 -1
  35. data/lib/aspera/markdown.rb +1 -1
  36. data/lib/aspera/persistency_folder.rb +1 -1
  37. data/lib/aspera/rest.rb +34 -49
  38. data/lib/aspera/rest_list.rb +116 -0
  39. data/lib/aspera/sync/operations.rb +1 -1
  40. data/lib/aspera/transfer/parameters.rb +8 -8
  41. data/lib/aspera/transfer/spec.rb +1 -0
  42. data/lib/aspera/yaml.rb +1 -1
  43. data.tar.gz.sig +0 -0
  44. metadata +4 -3
  45. metadata.gz.sig +0 -0
@@ -194,7 +194,9 @@ module Aspera
194
194
  [error_msg, 'Use:'].concat(accept_list.map{ |c| "- #{c}"}.sort).join("\n")
195
195
  end
196
196
 
197
- # change option name with dash to name with underscore
197
+ # Change option name with dash to name with underscore
198
+ # @param name [String] option name
199
+ # @return [String]
198
200
  def option_line_to_name(name)
199
201
  return name.gsub(OPTION_SEP_LINE, OPTION_SEP_SYMBOL)
200
202
  end
@@ -211,7 +213,7 @@ module Aspera
211
213
  def initialize(program_name, argv = nil)
212
214
  # command line values *not* starting with '-'
213
215
  @unprocessed_cmd_line_arguments = []
214
- # command line values starting with '-'
216
+ # command line values starting with at least one '-'
215
217
  @unprocessed_cmd_line_options = []
216
218
  # a copy of all initial options
217
219
  @initial_cli_options = []
@@ -330,7 +332,7 @@ module Aspera
330
332
 
331
333
  # @param descr [String] description for help
332
334
  # @param mandatory [Boolean] if true, raise error if option not set
333
- # @param multiple [Boolean] if true, return remaining arguments (Array)
335
+ # @param multiple [Boolean] if true, return remaining arguments (Array) until END
334
336
  # @param accept_list [Array, NilClass] list of allowed values (Symbol)
335
337
  # @param validation [Class, Array, NilClass] Accepted value type(s) or list of Symbols
336
338
  # @param aliases [Hash] map of aliases: key = alias, value = real value
@@ -345,10 +347,19 @@ module Aspera
345
347
  descr = "#{descr} (#{validation.join(', ')})" unless validation.nil? || validation.eql?(Allowed::TYPES_STRING)
346
348
  result =
347
349
  if !@unprocessed_cmd_line_arguments.empty?
348
- how_many = multiple ? @unprocessed_cmd_line_arguments.length : 1
349
- values = @unprocessed_cmd_line_arguments.shift(how_many)
350
+ if multiple
351
+ index = @unprocessed_cmd_line_arguments.index(SpecialValues::EOA)
352
+ if index.nil?
353
+ values = @unprocessed_cmd_line_arguments.shift(@unprocessed_cmd_line_arguments.length)
354
+ else
355
+ values = @unprocessed_cmd_line_arguments.shift(index)
356
+ @unprocessed_cmd_line_arguments.shift # remove EOA
357
+ end
358
+ else
359
+ values = [@unprocessed_cmd_line_arguments.shift]
360
+ end
350
361
  values = values.map{ |v| ExtendedValue.instance.evaluate(v, context: "argument: #{descr}", allowed: validation)}
351
- # if expecting list and only one arg of type array : it is the list
362
+ # If expecting list and only one arg of type array : it is the list
352
363
  values = values.first if multiple && values.length.eql?(1) && values.first.is_a?(Array)
353
364
  if accept_list
354
365
  allowed_values = [].concat(accept_list)
@@ -457,21 +468,17 @@ module Aspera
457
468
  # @return [Hash] options as taken from config file and command line just before command execution
458
469
  def unprocessed_options_with_value
459
470
  result = {}
460
- @initial_cli_options.each do |option_value|
461
- case option_value
462
- when /^#{OPTION_PREFIX}([^=]+)$/o
463
- # ignore
464
- when /^#{OPTION_PREFIX}([^=]+)=(.*)$/o
465
- name = Regexp.last_match(1)
466
- value = Regexp.last_match(2)
467
- name.gsub!(OPTION_SEP_LINE, OPTION_SEP_SYMBOL)
468
- value = ExtendedValue.instance.evaluate(value, context: "option: #{name}")
469
- Log.log.debug{"option #{name}=#{value}"}
470
- result[name] = value
471
- @unprocessed_cmd_line_options.delete(option_value)
472
- else
473
- raise Cli::BadArgument, "wrong option format: #{option_value}"
474
- end
471
+ @initial_cli_options.each do |option_argument|
472
+ # ignore short options
473
+ next unless option_argument.start_with?(OPTION_PREFIX)
474
+ name, value = option_argument[OPTION_PREFIX.length..-1].split(OPTION_VALUE_SEPARATOR, 2)
475
+ # ignore options without value
476
+ next if value.nil?
477
+ Log.log.debug{"option #{name}=#{value}"}
478
+ path = name.split(DotContainer::SEPARATOR)
479
+ path[0] = self.class.option_line_to_name(path[0])
480
+ DotContainer.dotted_to_container(path, smart_convert(value), result)
481
+ @unprocessed_cmd_line_options.delete(option_argument)
475
482
  end
476
483
  return result
477
484
  end
@@ -507,20 +514,25 @@ module Aspera
507
514
  rescue OptionParser::InvalidOption => e
508
515
  Log.log.trace1{"InvalidOption #{e}".red}
509
516
  # An option like --a.b.c=d does: a={"b":{"c":ext_val(d)}}
510
- if (m = e.args.first.match(/^--([a-z\-]+)\.([^=]+)=(.+)$/))
511
- option, path, value = m.captures
512
- option_sym = self.class.option_line_to_name(option).to_sym
513
- if @declared_options.key?(option_sym)
514
- set_option(option_sym, DotContainer.dotted_to_container(path, smart_convert(value), get_option(option_sym)), where: 'dotted')
515
- retry
517
+ if e.args.first.start_with?(OPTION_PREFIX)
518
+ name, value = e.args.first[OPTION_PREFIX.length..-1].split(OPTION_VALUE_SEPARATOR, 2)
519
+ if !value.nil?
520
+ path = name.split(DotContainer::SEPARATOR)
521
+ option_sym = self.class.option_line_to_name(path.shift)
522
+ if @declared_options.key?(option_sym)
523
+ # it's a known option, so let's process it
524
+ set_option(option_sym, DotContainer.dotted_to_container(path, smart_convert(value), get_option(option_sym)), where: 'dotted')
525
+ # resume to next
526
+ retry
527
+ end
516
528
  end
517
529
  end
518
- # save for later processing
530
+ # Save for later processing
519
531
  unknown_options.push(e.args.first)
520
532
  retry
521
533
  end
522
534
  Log.log.trace1{"remains: #{unknown_options}"}
523
- # set unprocessed options for next time
535
+ # Set unprocessed options for next time
524
536
  @unprocessed_cmd_line_options = unknown_options
525
537
  end
526
538
 
@@ -587,9 +599,9 @@ module Aspera
587
599
  ExtendedValue.assert_no_value(arg, :p)
588
600
  result = nil
589
601
  get_next_argument(:args, multiple: true).each do |arg|
590
- Aspera.assert(arg.include?(OPTION_VALUE_SEPARATOR)){"Positional argument: #{arg} does not inlude #{OPTION_VALUE_SEPARATOR}"}
602
+ Aspera.assert(arg.include?(OPTION_VALUE_SEPARATOR)){"Positional argument: #{arg} does not include #{OPTION_VALUE_SEPARATOR}"}
591
603
  path, value = arg.split(OPTION_VALUE_SEPARATOR, 2)
592
- result = DotContainer.dotted_to_container(path, smart_convert(value), result)
604
+ result = DotContainer.dotted_to_container(path.split(DotContainer::SEPARATOR), smart_convert(value), result)
593
605
  end
594
606
  result
595
607
  end
@@ -233,8 +233,7 @@ module Aspera
233
233
  defaults: {workspace: nil},
234
234
  scope: @scope,
235
235
  subpath: aoc_base_path,
236
- secret_finder: config,
237
- progress_disp: formatter
236
+ secret_finder: config
238
237
  ))
239
238
  end
240
239
 
@@ -256,7 +255,10 @@ module Aspera
256
255
  # @param hash [Hash,nil] Optional base `Hash` (modified)
257
256
  # @param string [Boolean] `true` to set key as `String`, else as `Symbol`
258
257
  # @param name [Boolean] Include name
259
- # @return [Hash] with key `workspace_[id,name]` (symbol or string) only if defined
258
+ # @return [Hash{Symbol, String => String}] the modified hash containing:
259
+ # * `workspace_id` [String] the unique identifier.
260
+ # * `workspace_name` [String] (optional) the name, included if +name+ is true.
261
+ # @note The key type (String or Symbol) depends on the +string+ parameter.
260
262
  def workspace_id_hash(hash = nil, string: false, name: false)
261
263
  info = aoc_api.workspace
262
264
  hash = {} if hash.nil?
@@ -742,7 +744,7 @@ module Aspera
742
744
  # Short link entity: `short_links` have:
743
745
  # - a numerical id, e.g. `764412`
744
746
  # - a resource type, e.g. `UrlToken`
745
- # - a ressource id, e.g. `scQ7uXPbvQ`
747
+ # - a resource id, e.g. `scQ7uXPbvQ`
746
748
  # - a short URL path, e.g. `dxyRpT9`
747
749
  # @param shared_data [Hash] Information for shared data: dropbox_id+name or file_id+node_id
748
750
  # @param &perm_block [Proc] Optional: create/modify/delete permissions on node
@@ -885,7 +887,7 @@ module Aspera
885
887
 
886
888
  def reject_packages_from_persistency(all_packages, skip_ids_persistency)
887
889
  return if skip_ids_persistency.nil?
888
- skip_package = skip_ids_persistency.data.each_with_object({}){ |i, m| m[i] = true}
890
+ skip_package = skip_ids_persistency.data.to_h{ |i| [i, true]}
889
891
  all_packages.reject!{ |pkg| skip_package[pkg['id']]}
890
892
  end
891
893
 
@@ -190,7 +190,7 @@ module Aspera
190
190
  return Main.result_single_object(api.read(one_res_path), fields: display_fields)
191
191
  when :list
192
192
  if tclo
193
- data, total = list_entities_limit_offset_total_count(api: api, entity:, items_key: items_key, query: query_read_delete(default: list_query))
193
+ data, total = api.list_entities_limit_offset_total_count(entity:, items_key: items_key, query: query_read_delete(default: list_query))
194
194
  return Main.result_object_list(data, total: total, fields: display_fields)
195
195
  end
196
196
  data, http = api.read(entity, query_read_delete, ret: :both)
@@ -259,86 +259,9 @@ module Aspera
259
259
  return value
260
260
  end
261
261
 
262
- # Get a (full or partial) list of all entities of a given type with query: offset/limit
263
- # @param api [Rest] API object
264
- # @param entity [String,Symbol] API endpoint of entity to list
265
- # @param items_key [String] Key in the result to get the list of items (Default: same as `entity`)
266
- # @param query [Hash,nil] Additional query parameters
267
- # @return [Array<(Array<Hash>, Integer)>] items, total_count
268
- def list_entities_limit_offset_total_count(
269
- api:,
270
- entity:,
271
- items_key: nil,
272
- query: nil
273
- )
274
- entity = entity.to_s if entity.is_a?(Symbol)
275
- items_key = entity.split('/').last if items_key.nil?
276
- query = {} if query.nil?
277
- Aspera.assert_type(entity, String)
278
- Aspera.assert_type(items_key, String)
279
- Aspera.assert_type(query, Hash)
280
- Log.log.debug{"list_entities t=#{entity} k=#{items_key} q=#{query}"}
281
- result = []
282
- offset = 0
283
- max_items = query.delete(MAX_ITEMS)
284
- remain_pages = query.delete(MAX_PAGES)
285
- # Merge default parameters, by default 100 per page
286
- query = {'limit'=> PER_PAGE_DEFAULT}.merge(query)
287
- total_count = nil
288
- loop do
289
- query['offset'] = offset
290
- page_result = api.read(entity, query)
291
- Aspera.assert_type(page_result[items_key], Array)
292
- result.concat(page_result[items_key])
293
- # Reach the limit set by user ?
294
- if !max_items.nil? && (result.length >= max_items)
295
- result = result.slice(0, max_items)
296
- break
297
- end
298
- total_count ||= page_result['total_count']
299
- break if result.length >= total_count
300
- remain_pages -= 1 unless remain_pages.nil?
301
- break if remain_pages == 0
302
- offset += page_result[items_key].length
303
- formatter.long_operation_running
304
- end
305
- formatter.long_operation_terminated
306
- return result, total_count
307
- end
308
-
309
- # Lookup an entity id from its name.
310
- # Uses query `q` if `query` is `:default` and `field` is `name`.
311
- # @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
312
- # @param value [String] Value to lookup
313
- # @param field [String] Field to match, by default it is `'name'`
314
- # @param items_key [String] Key in the result to get the list of items (override entity)
315
- # @param query [Hash] Additional query parameters (Default: `:default`)
316
- def lookup_entity_by_field(api:, entity:, value:, field: 'name', items_key: nil, query: :default)
317
- if query.eql?(:default)
318
- Aspera.assert(field.eql?('name')){'Default query is on name only'}
319
- query = {'q'=> value}
320
- end
321
- lookup_entity_generic(entity: entity, field: field, value: value){list_entities_limit_offset_total_count(api: api, entity: entity, items_key: items_key, query: query).first}
322
- end
323
-
324
- # Lookup entity by field and value. Extract single result from list of result returned by block.
325
- # @param entity [String] Type of entity to lookup, by default it is the path, and it is also the field name in result
326
- # @param value [String] Value to lookup
327
- # @param field [String] Field to match, by default it is `'name'`
328
- # @param block [Proc] Get list of entity matching query.
329
- def lookup_entity_generic(entity:, value:, field: 'name', &block)
330
- Aspera.assert(block_given?)
331
- found = yield
332
- Aspera.assert_array_all(found, Hash)
333
- found = found.select{ |i| i[field].eql?(value)}
334
- return found.first if found.length.eql?(1)
335
- raise Cli::BadIdentifier.new(entity, value, field: field, count: found.length)
336
- end
337
-
338
- PER_PAGE_DEFAULT = 1000
339
262
  # Percent selector: select by this field for this value
340
263
  REGEX_LOOKUP_ID_BY_FIELD = /^%([^:]+):(.*)$/
341
- private_constant :PER_PAGE_DEFAULT, :REGEX_LOOKUP_ID_BY_FIELD
264
+ private_constant :REGEX_LOOKUP_ID_BY_FIELD
342
265
  end
343
266
  end
344
267
  end
@@ -191,7 +191,8 @@ module Aspera
191
191
  def setup_rest_and_transfer_runtime
192
192
  RestParameters.instance.user_agent = Info::CMD_NAME
193
193
  RestParameters.instance.progress_bar = @progress_bar
194
- RestParameters.instance.session_cb = lambda{ |http_session| update_http_session(http_session)}
194
+ RestParameters.instance.session_cb = ->(http_session){update_http_session(http_session)}
195
+ RestParameters.instance.spinner_cb = ->(title = nil, action: :spin){formatter.long_operation(title, action: action)}
195
196
  # Check http options that are global
196
197
  keys_to_delete = []
197
198
  @option_http_options.each do |k, v|
@@ -211,7 +211,7 @@ module Aspera
211
211
  end
212
212
  # add special key
213
213
  package['items'] = package['link'].is_a?(Array) ? package['link'].length : 0
214
- package['metadata'] = package['metadata']['field'].each_with_object({}){ |i, m| m[i['name']] = i['content']}
214
+ package['metadata'] = package['metadata']['field'].to_h{ |i| [i['name'], i['content']]}
215
215
  # if we look for a specific package
216
216
  stop_condition = true if !stop_at_id.nil? && stop_at_id.eql?(package[PACKAGE_MATCH_FIELD])
217
217
  # keep only those for the specified recipient
@@ -108,16 +108,17 @@ module Aspera
108
108
  end
109
109
 
110
110
  # if recipient is just an email, then convert to expected API hash : name and type
111
- def normalize_recipients(parameters)
112
- return unless parameters.key?('recipients')
113
- Aspera.assert_type(parameters['recipients'], Array){'recipients'}
111
+ def normalize_recipients(parameters, type)
112
+ type = type.to_s
113
+ return unless parameters.key?(type)
114
+ Aspera.assert_type(parameters[type], Array){type}
114
115
  recipient_types = Api::Faspex::RECIPIENT_TYPES
115
116
  if parameters.key?('recipient_types')
116
117
  recipient_types = parameters['recipient_types']
117
118
  parameters.delete('recipient_types')
118
119
  recipient_types = [recipient_types] unless recipient_types.is_a?(Array)
119
120
  end
120
- parameters['recipients'].map! do |recipient_data|
121
+ parameters[type].map! do |recipient_data|
121
122
  # If just a string, make a general lookup and build expected name/type hash
122
123
  if recipient_data.is_a?(String)
123
124
  matched = @api_v5.lookup_by_name('contacts', recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
@@ -157,17 +158,17 @@ module Aspera
157
158
  end
158
159
  end
159
160
 
160
- # @param [Srting] job identifier
161
+ # @param [String] job identifier
161
162
  # @return [Hash] result of API call for job status
162
163
  def wait_for_job(job_id)
163
164
  result = nil
164
165
  loop do
165
166
  result = @api_v5.read("jobs/#{job_id}", {type: :formatted})
166
167
  break unless Api::Faspex::JOB_RUNNING.include?(result['status'])
167
- formatter.long_operation_running(result['status'])
168
+ RestParameters.instance.spinner_cb.call(result['status'])
168
169
  sleep(0.5)
169
170
  end
170
- formatter.long_operation_terminated
171
+ RestParameters.instance.spinner_cb.call(action: :success)
171
172
  return result
172
173
  end
173
174
 
@@ -182,16 +183,22 @@ module Aspera
182
183
  when *Api::Faspex::API_LIST_MAILBOX_TYPES then "#{box}/packages"
183
184
  else
184
185
  group_type = options.get_option(:group_type)
185
- "#{group_type}/#{lookup_entity_by_field(api: @api_v5, entity: group_type, value: box)['id']}/packages"
186
+ "#{group_type}/#{@api_v5.lookup_entity_by_field(entity: group_type, value: box)['id']}/packages"
186
187
  end
187
- list, total = list_entities_limit_offset_total_count(
188
- api: @api_v5,
189
- entity: entity,
190
- query: query_read_delete(default: query)
191
- )
188
+ list, total = @api_v5.list_entities_limit_offset_total_count(entity: entity, query: query_read_delete(default: query))
192
189
  return list.select(&filter), total
193
190
  end
194
191
 
192
+ # Build query to get package recipients based on package info in case of shared inbox or workgroup recipient
193
+ # @param package_id [String] the package id to get info from
194
+ def recipient_query(package_id)
195
+ package_info = @api_v5.read("packages/#{package_id}")
196
+ base_query = {}
197
+ base_query['recipient_workgroup_id'] = package_info['recipients'].first['id'] if WORKGROUP_TYPES.include?(package_info['recipients'].first['recipient_type'])
198
+ base_query['recipient_user_id'] = package_info['recipients'].first['id'] if package_info['recipients'].first['recipient_type'].eql?('user')
199
+ base_query
200
+ end
201
+
195
202
  def package_receive(package_ids)
196
203
  # prepare persistency if needed
197
204
  skip_ids_persistency = nil
@@ -241,7 +248,7 @@ module Aspera
241
248
  type: Api::Faspex.box_type(box),
242
249
  transfer_type: Api::Faspex::TRANSFER_CONNECT
243
250
  }
244
- download_params[:recipient_workgroup_id] = lookup_entity_by_field(api: @api_v5, entity: options.get_option(:group_type), value: box)['id'] if !Api::Faspex::API_LIST_MAILBOX_TYPES.include?(box) && box != SpecialValues::ALL
251
+ # download_params[:recipient_workgroup_id] = @api_v5.lookup_entity_by_field(entity: options.get_option(:group_type), value: box)['id'] if !Api::Faspex::API_LIST_MAILBOX_TYPES.include?(box) && box != SpecialValues::ALL
245
252
  packages.each do |package|
246
253
  pkg_id = package['id']
247
254
  formatter.display_status("Receiving package #{pkg_id}")
@@ -249,7 +256,7 @@ module Aspera
249
256
  transfer_spec = @api_v5.call(
250
257
  operation: 'POST',
251
258
  subpath: "packages/#{pkg_id}/transfer_spec/download",
252
- query: download_params,
259
+ query: download_params.merge(recipient_query(pkg_id)),
253
260
  content_type: Mime::JSON,
254
261
  body: param_file_list,
255
262
  headers: {'Accept' => Mime::JSON}
@@ -276,7 +283,7 @@ module Aspera
276
283
  recipient_type: @api_v5.pub_link_context['recipient_type']
277
284
  }]
278
285
  end
279
- normalize_recipients(parameters)
286
+ PACKAGE_RECIPIENT_TYPES.each{ |type| normalize_recipients(parameters, type)}
280
287
  # User specified content prot in tspec, but faspex requires in package creation
281
288
  # `transfer_spec/upload` will set `content_protection`
282
289
  if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
@@ -298,8 +305,7 @@ module Aspera
298
305
  else
299
306
  # send from remote shared folder
300
307
  if (m = Base.percent_selector(shared_folder))
301
- shared_folder = lookup_entity_by_field(
302
- api: @api_v5,
308
+ shared_folder = @api_v5.lookup_entity_by_field(
303
309
  entity: 'shared_folders',
304
310
  field: m[:field],
305
311
  value: m[:value]
@@ -319,9 +325,9 @@ module Aspera
319
325
 
320
326
  # Browse a folder
321
327
  # @param browse_endpoint [String] the endpoint to browse
322
- def browse_folder(browse_endpoint)
328
+ def browse_folder(browse_endpoint, base_query = {})
323
329
  folders_to_process = [options.get_next_argument('folder path', default: '/')]
324
- query = query_read_delete(default: {})
330
+ query = base_query.merge(query_read_delete(default: {}))
325
331
  filters = query.delete('filters'){{}}
326
332
  Aspera.assert_type(filters, Hash)
327
333
  filters['basenames'] ||= []
@@ -357,7 +363,7 @@ module Aspera
357
363
  end
358
364
  folders_to_process.concat(data['items'].select{ |i| i['type'].eql?('directory')}.map{ |i| i['path']}) if recursive
359
365
  if use_paging
360
- iteration_token = http[Api::Faspex::HEADER_ITERATION_TOKEN]
366
+ iteration_token = http[Api::Faspex::HEADER_X_NEXT_ITER_TOKEN]
361
367
  break if iteration_token.nil? || iteration_token.empty?
362
368
  query['iteration_token'] = iteration_token
363
369
  else
@@ -365,12 +371,11 @@ module Aspera
365
371
  break if data['item_count'].eql?(0)
366
372
  query['offset'] += data['item_count']
367
373
  end
368
- formatter.long_operation_running(all_items.count)
374
+ RestParameters.instance.spinner_cb.call(all_items.count)
369
375
  end
370
376
  query.delete('iteration_token')
371
377
  end
372
- formatter.long_operation_terminated
373
-
378
+ RestParameters.instance.spinner_cb.call(action: :success)
374
379
  return Main.result_object_list(all_items, total: total_count)
375
380
  end
376
381
 
@@ -384,7 +389,7 @@ module Aspera
384
389
  when :show
385
390
  return Main.result_single_object(@api_v5.read("packages/#{package_id}"))
386
391
  when :browse
387
- return browse_folder("packages/#{package_id}/files/#{Api::Faspex.box_type(options.get_option(:box))}")
392
+ return browse_folder("packages/#{package_id}/files/#{Api::Faspex.box_type(options.get_option(:box))}", recipient_query(package_id))
388
393
  when :status
389
394
  status_list = options.get_next_argument('list of states, or nothing', mandatory: false, validation: Array)
390
395
  status = wait_package_status(package_id, status_list: status_list)
@@ -447,18 +452,20 @@ module Aspera
447
452
  res_id_query = {'all': true}
448
453
  when :nodes
449
454
  available_commands += %i[shared_folders browse]
455
+ when :jobs
456
+ exec_args[:display_fields] = %w[id job_name job_type status]
450
457
  end
451
458
  res_command = options.get_next_command(available_commands)
452
459
  return Main.result_value_list(Api::Faspex::EMAIL_NOTIF_LIST, name: 'email_id') if res_command.eql?(:list) && res_sym.eql?(:email_notifications)
453
460
  case res_command
454
461
  when *ALL_OPS
455
462
  return entity_execute(command: res_command, **exec_args) do |field, value|
456
- lookup_entity_by_field(api: @api_v5, entity: exec_args[:entity], value: value, field: field, items_key: exec_args[:items_key], query: res_id_query)['id']
463
+ @api_v5.lookup_entity_by_field(entity: exec_args[:entity], value: value, field: field, items_key: exec_args[:items_key], query: res_id_query)['id']
457
464
  end
458
465
  when :shared_folders
459
466
  # nodes
460
467
  node_id = instance_identifier do |field, value|
461
- lookup_entity_by_field(api: @api_v5, entity: 'nodes', field: field, value: value)['id']
468
+ @api_v5.lookup_entity_by_field(entity: 'nodes', field: field, value: value)['id']
462
469
  end
463
470
  shfld_entity = "nodes/#{node_id}/shared_folders"
464
471
  sh_command = options.get_next_command(ALL_OPS + [:user])
@@ -469,33 +476,32 @@ module Aspera
469
476
  entity: shfld_entity,
470
477
  command: sh_command
471
478
  ) do |field, value|
472
- lookup_entity_by_field(api: @api_v5, entity: shfld_entity, field: field, value: value)['id']
479
+ @api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
473
480
  end
474
481
  when :user
475
482
  sh_id = instance_identifier do |field, value|
476
- lookup_entity_by_field(api: @api_v5, entity: shfld_entity, field: field, value: value)['id']
483
+ @api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
477
484
  end
478
485
  user_path = "#{shfld_entity}/#{sh_id}/custom_access_users"
479
486
  return entity_execute(api: @api_v5, entity: user_path, items_key: 'users') do |field, value|
480
- lookup_entity_by_field(api: @api_v5, entity: user_path, items_key: 'users', field: field, value: value)['id']
487
+ @api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: field, value: value)['id']
481
488
  end
482
489
 
483
490
  end
484
491
  when :browse
485
492
  # nodes
486
493
  node_id = instance_identifier do |field, value|
487
- lookup_entity_by_field(api: @api_v5, entity: 'nodes', value: value, field: field)['id']
494
+ @api_v5.lookup_entity_by_field(entity: 'nodes', value: value, field: field)['id']
488
495
  end
489
496
  return browse_folder("nodes/#{node_id}/browse")
490
497
  when :invite_external_collaborator
491
498
  # :shared_inboxes, :workgroups
492
- shared_inbox_id = instance_identifier{ |field, value| lookup_entity_by_field(api: @api_v5, entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
499
+ shared_inbox_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
493
500
  creation_payload = value_create_modify(command: res_command, type: [Hash, String])
494
501
  creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
495
502
  result = @api_v5.create("#{res_sym}/#{shared_inbox_id}/external_collaborator", creation_payload)
496
503
  formatter.display_status(result['message'])
497
- result = lookup_entity_by_field(
498
- api: @api_v5,
504
+ result = @api_v5.lookup_entity_by_field(
499
505
  entity: "#{res_sym}/#{shared_inbox_id}/members",
500
506
  items_key: 'members',
501
507
  value: creation_payload['email_address'],
@@ -504,7 +510,7 @@ module Aspera
504
510
  return Main.result_single_object(result)
505
511
  when :members, :saml_groups
506
512
  # res_command := :shared_inboxes, :workgroups
507
- res_id = instance_identifier{ |field, value| lookup_entity_by_field(api: @api_v5, entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
513
+ res_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: res_sym.to_s, field: field, value: value, query: res_id_query)['id']}
508
514
  res_path = "#{res_sym}/#{res_id}/#{res_command}"
509
515
  list_key = res_command.to_s
510
516
  list_key = 'groups' if res_command.eql?(:saml_groups)
@@ -515,8 +521,7 @@ module Aspera
515
521
  users = [users] unless users.is_a?(Array)
516
522
  users = users.map do |user|
517
523
  if (m = Base.percent_selector(user))
518
- lookup_entity_by_field(
519
- api: @api_v5,
524
+ @api_v5.lookup_entity_by_field(
520
525
  entity: 'accounts',
521
526
  field: m[:field],
522
527
  value: m[:value],
@@ -537,8 +542,7 @@ module Aspera
537
542
  command: sub_command,
538
543
  items_key: list_key
539
544
  ) do |field, value|
540
- lookup_entity_by_field(
541
- api: @api_v5,
545
+ @api_v5.lookup_entity_by_field(
542
546
  entity: res_path,
543
547
  field: field,
544
548
  value: value,
@@ -547,7 +551,7 @@ module Aspera
547
551
  end
548
552
  when :reset_password
549
553
  # :accounts
550
- contact_id = instance_identifier{ |field, value| lookup_entity_by_field(api: @api_v5, entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
554
+ contact_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
551
555
  @api_v5.create("accounts/#{contact_id}/reset_password", {})
552
556
  return Main.result_status('password reset, user shall check email')
553
557
  end
@@ -568,16 +572,10 @@ module Aspera
568
572
  event_type = options.get_next_command(%i[application webhook])
569
573
  case event_type
570
574
  when :application
571
- list, total = list_entities_limit_offset_total_count(
572
- api: @api_v5,
573
- entity: 'application_events',
574
- query: query_read_delete
575
- )
576
-
575
+ list, total = @api_v5.list_entities_limit_offset_total_count(entity: 'application_events', query: query_read_delete)
577
576
  return Main.result_object_list(list, total: total, fields: %w[event_type created_at application user.name])
578
577
  when :webhook
579
- list, total = list_entities_limit_offset_total_count(
580
- api: @api_v5,
578
+ list, total = @api_v5.list_entities_limit_offset_total_count(
581
579
  entity: 'all_webhooks_events',
582
580
  query: query_read_delete,
583
581
  items_key: 'events'
@@ -694,7 +692,7 @@ module Aspera
694
692
  items_key: invitation_endpoint,
695
693
  display_fields: %w[id public recipient_type recipient_name email_address]
696
694
  ) do |field, value|
697
- lookup_entity_by_field(api: @api_v5, entity: invitation_endpoint, field: field, value: value, query: {})['id']
695
+ @api_v5.lookup_entity_by_field(entity: invitation_endpoint, field: field, value: value, query: {})['id']
698
696
  end
699
697
  end
700
698
  when :gateway
@@ -719,8 +717,10 @@ module Aspera
719
717
  end
720
718
  SHARED_INBOX_MEMBER_LEVELS = %i[submit_only standard shared_inbox_admin].freeze
721
719
  ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
722
- CONTACT_TYPES = %w{workgroup shared_inbox distribution_list user external_user}.freeze
723
- private_constant :SHARED_INBOX_MEMBER_LEVELS, :ACCOUNT_TYPES, :CONTACT_TYPES
720
+ WORKGROUP_TYPES = %w{workgroup shared_inbox}.freeze
721
+ CONTACT_TYPES = (WORKGROUP_TYPES + %w{distribution_list user external_user}).freeze
722
+ PACKAGE_RECIPIENT_TYPES = %i{recipients private_recipients notified_on_upload notified_on_download notified_on_receipt}
723
+ private_constant :SHARED_INBOX_MEMBER_LEVELS, :ACCOUNT_TYPES, :CONTACT_TYPES, :PACKAGE_RECIPIENT_TYPES
724
724
  end
725
725
  end
726
726
  end
@@ -93,12 +93,9 @@ module Aspera
93
93
  options.declare(:validator, 'Identifier of validator (optional for central)')
94
94
  options.declare(:asperabrowserurl, 'URL for simple aspera web ui', default: 'https://asperabrowser.mybluemix.net')
95
95
  options.declare(
96
- :default_ports, 'Gen4: Use standard FASP ports (true) or get from node API (false)', allowed: Allowed::TYPES_BOOLEAN, default: true,
97
- handler: {o: Api::Node, m: :use_standard_ports}
98
- )
99
- options.declare(
100
- :node_cache, 'Gen4: Set to no to force actual file system read', allowed: Allowed::TYPES_BOOLEAN,
101
- handler: {o: Api::Node, m: :use_node_cache}
96
+ :node_api, 'Gen4: standard_ports: Use standard FASP ports (true) or get from node API (false). cache: Set to false to force actual file system read',
97
+ allowed: Hash,
98
+ handler: {o: Api::Node, m: :api_options}
102
99
  )
103
100
  options.declare(:root_id, 'Gen4: File id of top folder when using access key (override AK root id)')
104
101
  options.declare(:dynamic_key, 'Private key PEM to use for dynamic key auth', handler: {o: Api::Node, m: :use_dynamic_key})
@@ -242,14 +239,14 @@ module Aspera
242
239
  break if all_items.count >= total_count
243
240
  offset += items.count
244
241
  query['skip'] = offset
245
- formatter.long_operation_running(all_items.count)
242
+ RestParameters.instance.spinner_cb.call(all_items.count)
246
243
  end
247
244
  query.delete('skip')
248
245
  end
249
246
  @prefixer&.remove_in_object_list!(all_items)
250
247
  return Main.result_object_list(all_items)
251
248
  ensure
252
- formatter.long_operation_terminated
249
+ RestParameters.instance.spinner_cb.call(action: :success)
253
250
  end
254
251
 
255
252
  # Create async transfer spec request from direction and folders
@@ -388,7 +385,7 @@ module Aspera
388
385
  when :transport
389
386
  return Main.result_single_object(@api_node.transport_params)
390
387
  when :spec
391
- return Main.result_single_object(@api_node.base_spec)
388
+ return Main.result_single_object(@api_node.base_spec, fields: Formatter.all_but(Transfer::Spec::SPECIFIC))
392
389
  end
393
390
  Aspera.error_unreachable_line
394
391
  end
@@ -523,7 +520,7 @@ module Aspera
523
520
  return Main.result_text(result[:password])
524
521
  when :browse
525
522
  apifid = apifid_from_next_arg(top_file_id)
526
- file_info = apifid[:api].read("files/#{apifid[:file_id]}", **Api::Node.cache_control)
523
+ file_info = apifid[:api].read("files/#{apifid[:file_id]}", headers: Api::Node.add_cache_control)
527
524
  unless file_info['type'].eql?('folder')
528
525
  # a single file
529
526
  return Main.result_object_list([file_info], fields: GEN4_LS_FIELDS)
@@ -931,10 +928,8 @@ module Aspera
931
928
  # do not process last one
932
929
  break if end_date.nil?
933
930
  # init data for this period
934
- period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).each_with_object({}) do |direction, h|
935
- h[direction] = dir_info.each_with_object({}) do |k2, h2|
936
- h2[k2] = 0
937
- end
931
+ period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).to_h do |direction|
932
+ [direction, dir_info.to_h{ |k2| [k2, 0]}]
938
933
  end
939
934
  # find all transfers that were active at this time
940
935
  transfers_data.each do |transfer|