aspera-cli 4.25.4 → 4.25.5

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.
@@ -100,24 +100,18 @@ module Aspera
100
100
  options.parse_options!
101
101
  end
102
102
 
103
- def set_api
104
- # create an API object with the same options, but with a different subpath
105
- @api_v5 = Api::Faspex.new(**Oauth.args_from_options(options))
106
- # in case user wants to use HTTPGW tell transfer agent how to get address
107
- transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
108
- end
109
-
110
103
  # 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'}
104
+ def normalize_recipients(parameters, type)
105
+ type = type.to_s
106
+ return unless parameters.key?(type)
107
+ Aspera.assert_type(parameters[type], Array){type}
114
108
  recipient_types = Api::Faspex::RECIPIENT_TYPES
115
109
  if parameters.key?('recipient_types')
116
110
  recipient_types = parameters['recipient_types']
117
111
  parameters.delete('recipient_types')
118
112
  recipient_types = [recipient_types] unless recipient_types.is_a?(Array)
119
113
  end
120
- parameters['recipients'].map! do |recipient_data|
114
+ parameters[type].map! do |recipient_data|
121
115
  # If just a string, make a general lookup and build expected name/type hash
122
116
  if recipient_data.is_a?(String)
123
117
  matched = @api_v5.lookup_by_name('contacts', recipient_data, query: Rest.php_style({context: 'packages', type: recipient_types}))
@@ -157,7 +151,7 @@ module Aspera
157
151
  end
158
152
  end
159
153
 
160
- # @param [Srting] job identifier
154
+ # @param [String] job identifier
161
155
  # @return [Hash] result of API call for job status
162
156
  def wait_for_job(job_id)
163
157
  result = nil
@@ -182,13 +176,9 @@ module Aspera
182
176
  when *Api::Faspex::API_LIST_MAILBOX_TYPES then "#{box}/packages"
183
177
  else
184
178
  group_type = options.get_option(:group_type)
185
- "#{group_type}/#{lookup_entity_by_field(api: @api_v5, entity: group_type, value: box)['id']}/packages"
179
+ "#{group_type}/#{@api_v5.lookup_entity_by_field(entity: group_type, value: box)['id']}/packages"
186
180
  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
- )
181
+ list, total = @api_v5.list_entities_limit_offset_total_count(entity: entity, query: query_read_delete(default: query))
192
182
  return list.select(&filter), total
193
183
  end
194
184
 
@@ -251,7 +241,7 @@ module Aspera
251
241
  type: Api::Faspex.box_type(box),
252
242
  transfer_type: Api::Faspex::TRANSFER_CONNECT
253
243
  }
254
- # 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
244
+ # 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
255
245
  packages.each do |package|
256
246
  pkg_id = package['id']
257
247
  formatter.display_status("Receiving package #{pkg_id}")
@@ -286,7 +276,7 @@ module Aspera
286
276
  recipient_type: @api_v5.pub_link_context['recipient_type']
287
277
  }]
288
278
  end
289
- normalize_recipients(parameters)
279
+ PACKAGE_RECIPIENT_TYPES.each{ |type| normalize_recipients(parameters, type)}
290
280
  # User specified content prot in tspec, but faspex requires in package creation
291
281
  # `transfer_spec/upload` will set `content_protection`
292
282
  if transfer.user_transfer_spec['content_protection'] && !parameters.key?('ear_enabled')
@@ -308,8 +298,7 @@ module Aspera
308
298
  else
309
299
  # send from remote shared folder
310
300
  if (m = Base.percent_selector(shared_folder))
311
- shared_folder = lookup_entity_by_field(
312
- api: @api_v5,
301
+ shared_folder = @api_v5.lookup_entity_by_field(
313
302
  entity: 'shared_folders',
314
303
  field: m[:field],
315
304
  value: m[:value]
@@ -336,7 +325,7 @@ module Aspera
336
325
  Aspera.assert_type(filters, Hash)
337
326
  filters['basenames'] ||= []
338
327
  Aspera.assert_type(filters, Hash){'filters'}
339
- max_items = query.delete(MAX_ITEMS)
328
+ max_items = query.delete(RestList::MAX_ITEMS)
340
329
  recursive = query.delete('recursive')
341
330
  use_paging = query.delete('paging'){true}
342
331
  if use_paging
@@ -427,8 +416,7 @@ module Aspera
427
416
  def execute_resource(res_sym)
428
417
  exec_args = {
429
418
  api: @api_v5,
430
- entity: res_sym.to_s,
431
- tclo: true
419
+ entity: res_sym.to_s
432
420
  }
433
421
  res_id_query = :default
434
422
  available_commands = ALL_OPS
@@ -449,7 +437,7 @@ module Aspera
449
437
  available_commands += [:reset_password]
450
438
  when :oauth_clients
451
439
  exec_args[:display_fields] = Formatter.all_but('public_key')
452
- exec_args[:api] = @api_v5.auth_api
440
+ exec_args[:api] = Api::Faspex.new(root: Api::Faspex::PATH_AUTH, **Oauth.args_from_options(options))
453
441
  exec_args[:list_query] = {'expand': true, 'no_api_path': true, 'client_types[]': 'public'}
454
442
  when :shared_inboxes, :workgroups
455
443
  available_commands += %i[members saml_groups invite_external_collaborator]
@@ -462,14 +450,17 @@ module Aspera
462
450
  res_command = options.get_next_command(available_commands)
463
451
  return Main.result_value_list(Api::Faspex::EMAIL_NOTIF_LIST, name: 'email_id') if res_command.eql?(:list) && res_sym.eql?(:email_notifications)
464
452
  case res_command
465
- when *ALL_OPS
453
+ when :create, :modify, :delete, :show
466
454
  return entity_execute(command: res_command, **exec_args) do |field, value|
467
- 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']
455
+ @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']
468
456
  end
457
+ when :list
458
+ data, total = exec_args[:api].list_entities_limit_offset_total_count(entity: exec_args[:entity], items_key: exec_args[:items_key], query: query_read_delete(default: exec_args[:list_query]))
459
+ return Main.result_object_list(data, total: total, fields: exec_args[:display_fields])
469
460
  when :shared_folders
470
461
  # nodes
471
462
  node_id = instance_identifier do |field, value|
472
- lookup_entity_by_field(api: @api_v5, entity: 'nodes', field: field, value: value)['id']
463
+ @api_v5.lookup_entity_by_field(entity: 'nodes', field: field, value: value)['id']
473
464
  end
474
465
  shfld_entity = "nodes/#{node_id}/shared_folders"
475
466
  sh_command = options.get_next_command(ALL_OPS + [:user])
@@ -480,33 +471,32 @@ module Aspera
480
471
  entity: shfld_entity,
481
472
  command: sh_command
482
473
  ) do |field, value|
483
- lookup_entity_by_field(api: @api_v5, entity: shfld_entity, field: field, value: value)['id']
474
+ @api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
484
475
  end
485
476
  when :user
486
477
  sh_id = instance_identifier do |field, value|
487
- lookup_entity_by_field(api: @api_v5, entity: shfld_entity, field: field, value: value)['id']
478
+ @api_v5.lookup_entity_by_field(entity: shfld_entity, field: field, value: value)['id']
488
479
  end
489
480
  user_path = "#{shfld_entity}/#{sh_id}/custom_access_users"
490
481
  return entity_execute(api: @api_v5, entity: user_path, items_key: 'users') do |field, value|
491
- lookup_entity_by_field(api: @api_v5, entity: user_path, items_key: 'users', field: field, value: value)['id']
482
+ @api_v5.lookup_entity_by_field(entity: user_path, items_key: 'users', field: field, value: value)['id']
492
483
  end
493
484
 
494
485
  end
495
486
  when :browse
496
487
  # nodes
497
488
  node_id = instance_identifier do |field, value|
498
- lookup_entity_by_field(api: @api_v5, entity: 'nodes', value: value, field: field)['id']
489
+ @api_v5.lookup_entity_by_field(entity: 'nodes', value: value, field: field)['id']
499
490
  end
500
491
  return browse_folder("nodes/#{node_id}/browse")
501
492
  when :invite_external_collaborator
502
493
  # :shared_inboxes, :workgroups
503
- 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']}
494
+ 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']}
504
495
  creation_payload = value_create_modify(command: res_command, type: [Hash, String])
505
496
  creation_payload = {'email_address' => creation_payload} if creation_payload.is_a?(String)
506
497
  result = @api_v5.create("#{res_sym}/#{shared_inbox_id}/external_collaborator", creation_payload)
507
498
  formatter.display_status(result['message'])
508
- result = lookup_entity_by_field(
509
- api: @api_v5,
499
+ result = @api_v5.lookup_entity_by_field(
510
500
  entity: "#{res_sym}/#{shared_inbox_id}/members",
511
501
  items_key: 'members',
512
502
  value: creation_payload['email_address'],
@@ -515,7 +505,7 @@ module Aspera
515
505
  return Main.result_single_object(result)
516
506
  when :members, :saml_groups
517
507
  # res_command := :shared_inboxes, :workgroups
518
- 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']}
508
+ 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']}
519
509
  res_path = "#{res_sym}/#{res_id}/#{res_command}"
520
510
  list_key = res_command.to_s
521
511
  list_key = 'groups' if res_command.eql?(:saml_groups)
@@ -526,8 +516,7 @@ module Aspera
526
516
  users = [users] unless users.is_a?(Array)
527
517
  users = users.map do |user|
528
518
  if (m = Base.percent_selector(user))
529
- lookup_entity_by_field(
530
- api: @api_v5,
519
+ @api_v5.lookup_entity_by_field(
531
520
  entity: 'accounts',
532
521
  field: m[:field],
533
522
  value: m[:value],
@@ -548,8 +537,7 @@ module Aspera
548
537
  command: sub_command,
549
538
  items_key: list_key
550
539
  ) do |field, value|
551
- lookup_entity_by_field(
552
- api: @api_v5,
540
+ @api_v5.lookup_entity_by_field(
553
541
  entity: res_path,
554
542
  field: field,
555
543
  value: value,
@@ -558,7 +546,7 @@ module Aspera
558
546
  end
559
547
  when :reset_password
560
548
  # :accounts
561
- contact_id = instance_identifier{ |field, value| lookup_entity_by_field(api: @api_v5, entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
549
+ contact_id = instance_identifier{ |field, value| @api_v5.lookup_entity_by_field(entity: 'accounts', field: field, value: value, query: res_id_query)['id']}
562
550
  @api_v5.create("accounts/#{contact_id}/reset_password", {})
563
551
  return Main.result_status('password reset, user shall check email')
564
552
  end
@@ -579,16 +567,10 @@ module Aspera
579
567
  event_type = options.get_next_command(%i[application webhook])
580
568
  case event_type
581
569
  when :application
582
- list, total = list_entities_limit_offset_total_count(
583
- api: @api_v5,
584
- entity: 'application_events',
585
- query: query_read_delete
586
- )
587
-
570
+ list, total = @api_v5.list_entities_limit_offset_total_count(entity: 'application_events', query: query_read_delete)
588
571
  return Main.result_object_list(list, total: total, fields: %w[event_type created_at application user.name])
589
572
  when :webhook
590
- list, total = list_entities_limit_offset_total_count(
591
- api: @api_v5,
573
+ list, total = @api_v5.list_entities_limit_offset_total_count(
592
574
  entity: 'all_webhooks_events',
593
575
  query: query_read_delete,
594
576
  items_key: 'events'
@@ -633,7 +615,12 @@ module Aspera
633
615
 
634
616
  def execute_action
635
617
  command = options.get_next_command(ACTIONS)
636
- set_api unless %i{postprocessing health}.include?(command)
618
+ unless %i{postprocessing health}.include?(command)
619
+ # create an API object with the same options, but with a different subpath
620
+ @api_v5 = Api::Faspex.new(**Oauth.args_from_options(options))
621
+ # in case user wants to use HTTPGW tell transfer agent how to get address
622
+ transfer.httpgw_url_cb = lambda{@api_v5.read('account')['gateway_url']}
623
+ end
637
624
  case command
638
625
  when :version
639
626
  return Main.result_single_object(@api_v5.read('version'))
@@ -705,7 +692,7 @@ module Aspera
705
692
  items_key: invitation_endpoint,
706
693
  display_fields: %w[id public recipient_type recipient_name email_address]
707
694
  ) do |field, value|
708
- 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']
709
696
  end
710
697
  end
711
698
  when :gateway
@@ -732,7 +719,8 @@ module Aspera
732
719
  ACCOUNT_TYPES = %w{local_user saml_user self_registered_user external_user}.freeze
733
720
  WORKGROUP_TYPES = %w{workgroup shared_inbox}.freeze
734
721
  CONTACT_TYPES = (WORKGROUP_TYPES + %w{distribution_list user external_user}).freeze
735
- private_constant :SHARED_INBOX_MEMBER_LEVELS, :ACCOUNT_TYPES, :CONTACT_TYPES
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
736
724
  end
737
725
  end
738
726
  end
@@ -11,6 +11,7 @@ require 'aspera/id_generator'
11
11
  require 'aspera/api/node'
12
12
  require 'aspera/oauth'
13
13
  require 'aspera/node_simulator'
14
+ require 'aspera/rest_list'
14
15
  require 'aspera/assert'
15
16
  require 'base64'
16
17
  require 'zlib'
@@ -166,7 +167,7 @@ module Aspera
166
167
  # @param api [Rest] an existing API object for the Node API
167
168
  # @param prefix_path [String,nil] for Faspex 4, allows browsing a package without full path in node (removes storage prefix)
168
169
  def initialize(context:, api: nil, prefix_path: nil)
169
- @prefixer = prefix_path ? NodePathPrefix.new(prefix_path) : nil
170
+ @node_path_prefix = prefix_path ? NodePathPrefix.new(prefix_path) : nil
170
171
  super(context: context, basic_options: api.nil?)
171
172
  Node.declare_options(options)
172
173
  return if context.only_manual?
@@ -196,11 +197,11 @@ module Aspera
196
197
  # Gen3 API
197
198
  def browse_gen3
198
199
  folders_to_process = options.get_next_argument('path', validation: String)
199
- folders_to_process = @prefixer.add_to_path(folders_to_process) unless @prefixer.nil?
200
+ folders_to_process = @node_path_prefix.add_to_path(folders_to_process) unless @node_path_prefix.nil?
200
201
  folders_to_process = [folders_to_process]
201
202
  query = options.get_option(:query) || {}
202
203
  # special parameter: max number of entries in result
203
- max_items = query.delete(MAX_ITEMS)
204
+ max_items = query.delete(RestList::MAX_ITEMS)
204
205
  # special parameter: recursive browsing
205
206
  recursive = query.delete('recursive')
206
207
  # special parameter: only return one entry for the path, even if folder
@@ -221,7 +222,7 @@ module Aspera
221
222
  response = @api_node.create('files/browse', query)
222
223
  # 'file','symbolic_link'
223
224
  if !Node.gen3_entry_folder?(response['self']) || only_path
224
- @prefixer&.remove_in_object_list!([response['self']])
225
+ @node_path_prefix&.remove_in_object_list!([response['self']])
225
226
  return Main.result_single_object(response['self'])
226
227
  end
227
228
  items = response['items']
@@ -243,7 +244,7 @@ module Aspera
243
244
  end
244
245
  query.delete('skip')
245
246
  end
246
- @prefixer&.remove_in_object_list!(all_items)
247
+ @node_path_prefix&.remove_in_object_list!(all_items)
247
248
  return Main.result_object_list(all_items)
248
249
  ensure
249
250
  RestParameters.instance.spinner_cb.call(action: :success)
@@ -286,12 +287,12 @@ module Aspera
286
287
  when :delete
287
288
  # TODO: add query for recursive
288
289
  paths_to_delete = options.get_next_argument('file list', multiple: true)
289
- @prefixer&.add_to_paths!(paths_to_delete)
290
+ @node_path_prefix&.add_to_paths!(paths_to_delete)
290
291
  resp = @api_node.create('files/delete', {paths: paths_to_delete.map{ |i| {'path' => i.start_with?('/') ? i : "/#{i}"}}})
291
292
  return cli_result_from_paths_response(resp, 'file deleted')
292
293
  when :search
293
294
  search_root = options.get_next_argument('search root', validation: String)
294
- search_root = @prefixer.add_to_path(search_root) unless @prefixer.nil?
295
+ search_root = @node_path_prefix.add_to_path(search_root) unless @node_path_prefix.nil?
295
296
  parameters = {'path' => search_root}
296
297
  other_options = options.get_option(:query)
297
298
  parameters.merge!(other_options) unless other_options.nil?
@@ -300,40 +301,40 @@ module Aspera
300
301
  fields = resp['items'].first.keys.reject{ |i| SEARCH_REMOVE_FIELDS.include?(i)}
301
302
  formatter.display_item_count(resp['item_count'], resp['total_count'])
302
303
  formatter.display_status("params: #{resp['parameters'].keys.map{ |k| "#{k}:#{resp['parameters'][k]}"}.join(',')}")
303
- @prefixer&.remove_in_object_list!(resp['items'])
304
+ @node_path_prefix&.remove_in_object_list!(resp['items'])
304
305
  return Main.result_object_list(resp['items'], fields: fields)
305
306
  when :space
306
307
  path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
307
- @prefixer&.add_to_paths!(path_list)
308
+ @node_path_prefix&.add_to_paths!(path_list)
308
309
  resp = @api_node.create('space', {'paths' => path_list.map{ |i| {path: i}}})
309
- @prefixer&.remove_in_object_list!(resp['paths'])
310
+ @node_path_prefix&.remove_in_object_list!(resp['paths'])
310
311
  return Main.result_object_list(resp['paths'])
311
312
  when :mkdir
312
313
  path_list = options.get_next_argument('folder path or ext.val. list', multiple: true)
313
- @prefixer&.add_to_paths!(path_list)
314
+ @node_path_prefix&.add_to_paths!(path_list)
314
315
  resp = @api_node.create('files/create', {'paths' => path_list.map{ |i| {type: :directory, path: i}}})
315
316
  return cli_result_from_paths_response(resp, 'folder created')
316
317
  when :mklink
317
318
  target = options.get_next_argument('target', validation: String)
318
- target = @prefixer.add_to_path(target) unless @prefixer.nil?
319
+ target = @node_path_prefix.add_to_path(target) unless @node_path_prefix.nil?
319
320
  one_path = options.get_next_argument('link path', validation: String)
320
- one_path = @prefixer.add_to_path(one_path) unless @prefixer.nil?
321
+ one_path = @node_path_prefix.add_to_path(one_path) unless @node_path_prefix.nil?
321
322
  resp = @api_node.create('files/create', {'paths' => [{type: :symbolic_link, path: one_path, target: {path: target}}]})
322
323
  return cli_result_from_paths_response(resp, 'link created')
323
324
  when :mkfile
324
325
  one_path = options.get_next_argument('file path', validation: String)
325
- one_path = @prefixer.add_to_path(one_path) unless @prefixer.nil?
326
+ one_path = @node_path_prefix.add_to_path(one_path) unless @node_path_prefix.nil?
326
327
  contents64 = Base64.strict_encode64(options.get_next_argument('contents'))
327
328
  resp = @api_node.create('files/create', {'paths' => [{type: :file, path: one_path, contents: contents64}]})
328
329
  return cli_result_from_paths_response(resp, 'file created')
329
330
  when :rename
330
331
  # TODO: multiple ?
331
332
  path_base = options.get_next_argument('path_base', validation: String)
332
- path_base = @prefixer.add_to_path(path_base) unless @prefixer.nil?
333
+ path_base = @node_path_prefix.add_to_path(path_base) unless @node_path_prefix.nil?
333
334
  path_src = options.get_next_argument('path_src', validation: String)
334
- path_src = @prefixer.add_to_path(path_src) unless @prefixer.nil?
335
+ path_src = @node_path_prefix.add_to_path(path_src) unless @node_path_prefix.nil?
335
336
  path_dst = options.get_next_argument('path_dst', validation: String)
336
- path_dst = @prefixer.add_to_path(path_dst) unless @prefixer.nil?
337
+ path_dst = @node_path_prefix.add_to_path(path_dst) unless @node_path_prefix.nil?
337
338
  resp = @api_node.create('files/rename', {'paths' => [{'path' => path_base, 'source' => path_src, 'destination' => path_dst}]})
338
339
  return cli_result_from_paths_response(resp, 'entry moved')
339
340
  when :browse
@@ -378,7 +379,7 @@ module Aspera
378
379
  return Main.result_transfer(transfer.start(transfer_spec))
379
380
  when :cat
380
381
  remote_path = options.get_next_argument('remote path', validation: String)
381
- remote_path = @prefixer.add_to_path(remote_path) unless @prefixer.nil?
382
+ remote_path = @node_path_prefix.add_to_path(remote_path) unless @node_path_prefix.nil?
382
383
  File.basename(remote_path)
383
384
  http = @api_node.read("files/#{URI.encode_www_form_component(remote_path)}/contents", ret: :resp)
384
385
  return Main.result_text(http.body)
@@ -882,10 +883,10 @@ module Aspera
882
883
  iteration_persistency.save
883
884
  return Main.result_status('Persistency reset')
884
885
  end
886
+ else
887
+ Aspera.assert(!transfer_filter.key?('reset'), type: Cli::BadArgument){'reset only with once_only'}
885
888
  end
886
- raise Cli::BadArgument, 'reset only with once_only' if transfer_filter.key?('reset') && iteration_persistency.nil?
887
- max_items = transfer_filter.delete(MAX_ITEMS)
888
- transfers_data = call_with_iteration(api: @api_node, operation: 'GET', subpath: 'ops/transfers', max: max_items, query: transfer_filter, iteration: iteration_persistency&.data)
889
+ transfers_data = @api_node.read_with_paging('ops/transfers', transfer_filter, iteration: iteration_persistency&.data)
889
890
  iteration_persistency&.save
890
891
  return Main.result_object_list(transfers_data, fields: %w[id status start_spec.direction start_spec.remote_user start_spec.remote_host start_spec.destination_path])
891
892
  when :sessions
@@ -928,10 +929,8 @@ module Aspera
928
929
  # do not process last one
929
930
  break if end_date.nil?
930
931
  # init data for this period
931
- period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).each_with_object({}) do |direction, h|
932
- h[direction] = dir_info.each_with_object({}) do |k2, h2|
933
- h2[k2] = 0
934
- end
932
+ period_bandwidth = Transfer::Spec::DIRECTION_ENUM_VALUES.map(&:to_sym).to_h do |direction|
933
+ [direction, dir_info.to_h{ |k2| [k2, 0]}]
935
934
  end
936
935
  # find all transfers that were active at this time
937
936
  transfers_data.each do |transfer|
@@ -1094,7 +1093,7 @@ module Aspera
1094
1093
  }
1095
1094
  loop do
1096
1095
  timestamp = Time.now
1097
- transfers_data = call_with_iteration(api: @api_node, operation: 'GET', subpath: 'ops/transfers', query: {active_only: true})
1096
+ transfers_data = @api_node.read_with_paging('ops/transfers', {active_only: true})
1098
1097
  datapoint[:asInt] = transfers_data.length
1099
1098
  datapoint[:timeUnixNano] = timestamp.to_i * 1_000_000_000 + timestamp.nsec
1100
1099
  Log.log.info("#{datapoint[:asInt]} active transfers")
@@ -1132,50 +1131,9 @@ module Aspera
1132
1131
  # Translates paths results into CLI result, and removes prefix
1133
1132
  def cli_result_from_paths_response(response, success_msg)
1134
1133
  obj_list = response_to_result(response, success_msg)
1135
- @prefixer&.remove_in_object_list!(obj_list)
1134
+ @node_path_prefix&.remove_in_object_list!(obj_list)
1136
1135
  return Main.result_object_list(obj_list, fields: %w[path result])
1137
1136
  end
1138
-
1139
- # Executes the provided API call in loop
1140
- # @param api [Rest] the API to call
1141
- # @param iteration [Array] a single element array with the iteration token or nil
1142
- # @param max [Integer] maximum number of items to return, or nil for no limit
1143
- # @param query [Hash] query parameters to use for the API call
1144
- # @param call_args [Hash] additional arguments to pass to the API call
1145
- # @return [Array] list of items returned by the API call
1146
- def call_with_iteration(api:, iteration: nil, max: nil, query: nil, **call_args)
1147
- Aspera.assert_type(iteration, Array, NilClass){'iteration'}
1148
- Aspera.assert_type(query, Hash, NilClass){'query'}
1149
- query_token = query&.dup || {}
1150
- item_list = []
1151
- query_token[:iteration_token] = iteration[0] unless iteration.nil?
1152
- loop do
1153
- data, http = api.call(**call_args, query: query_token, ret: :both)
1154
- Aspera.assert_type(data, Array){"Expected data to be an Array, got: #{data.class}"}
1155
- # no data
1156
- break if data.empty?
1157
- # get next iteration token from link
1158
- next_iteration_token = nil
1159
- link_info = http['Link']
1160
- unless link_info.nil?
1161
- m = link_info.match(/<([^>]+)>/)
1162
- Aspera.assert(m){"Cannot parse iteration in Link: #{link_info}"}
1163
- next_iteration_token = Rest.query_to_h(URI.parse(m[1]).query)['iteration_token']
1164
- end
1165
- # same as last iteration: stop
1166
- break if next_iteration_token&.eql?(query_token[:iteration_token])
1167
- query_token[:iteration_token] = next_iteration_token
1168
- item_list.concat(data)
1169
- if max&.<=(item_list.length)
1170
- item_list = item_list.slice(0, max)
1171
- break
1172
- end
1173
- break if next_iteration_token.nil?
1174
- end
1175
- # save iteration token if needed
1176
- iteration[0] = query_token[:iteration_token] unless iteration.nil?
1177
- item_list
1178
- end
1179
1137
  end
1180
1138
  end
1181
1139
  end
@@ -3,6 +3,8 @@
3
3
  require 'aspera/cli/plugins/basic_auth'
4
4
  require 'aspera/cli/plugins/node'
5
5
  require 'aspera/assert'
6
+ require 'aspera/rest_list'
7
+
6
8
  module Aspera
7
9
  module Cli
8
10
  module Plugins
@@ -128,7 +130,7 @@ module Aspera
128
130
  when :admin
129
131
  api_shares_admin = basic_auth_api(ADMIN_API_PATH)
130
132
  admin_command = options.get_next_command(%i[node share transfer_settings user group].freeze)
131
- lookup_share = ->(field, value){lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
133
+ lookup_share = ->(field, value){RestList.lookup_entity_generic(entity: 'share', field: field, value: value){api_shares_admin.read('data/shares')}['id']}
132
134
  case admin_command
133
135
  when :node
134
136
  return entity_execute(api: api_shares_admin, entity: 'data/nodes')
@@ -177,7 +179,7 @@ module Aspera
177
179
  entity_commands = %i[import].freeze
178
180
  end
179
181
  entity_verb = options.get_next_command(entity_commands)
180
- lookup_block = ->(field, value){lookup_entity_generic(entity: entity_type, field: field, value: value){api_shares_admin.read(entities_path)}['id']}
182
+ lookup_block = ->(field, value){RestList.lookup_entity_generic(entity: entity_type, field: field, value: value){api_shares_admin.read(entities_path)}['id']}
181
183
  case entity_verb
182
184
  when *ALL_OPS # list, show, delete, create, modify
183
185
  display_fields = entity_type.eql?(:user) ? %w[id user_id username first_name last_name email] : nil
@@ -33,6 +33,9 @@ module Aspera
33
33
  :DEFAULT_TRANSFER_NOTIFY_TEMPLATE
34
34
 
35
35
  class << self
36
+ # Analyze transfer session statuses and return a global status
37
+ #
38
+ # @param statuses [Array] list of session status, each status is :success or an error message string
36
39
  # @return [:success] if all sessions statuses returned by "start" are success
37
40
  # @return [Exception] if one sessions statuses returned by "start" is failed
38
41
  def session_status(statuses)
@@ -4,6 +4,6 @@ module Aspera
4
4
  module Cli
5
5
  # For beta add extension : .beta1
6
6
  # For dev version add extension : .pre
7
- VERSION = '4.25.4'
7
+ VERSION = '4.25.5'
8
8
  end
9
9
  end
@@ -7,13 +7,14 @@ module Aspera
7
7
  class DotContainer
8
8
  class << self
9
9
  # Insert extended value `value` into struct `result` at `path`
10
- # @param path [String] Dotted path in container
11
- # @param value [String] Last value to insert
12
- # @param result [NilClass, Hash, Array] current value
13
- # @return [Hash, Array]
10
+ # @param path [Array<String>] Path in container
11
+ # @param value [Object] Value to insert in deep container
12
+ # @param result [nil, Hash, Array] Current container to use (or nil to create a new one)
13
+ # @return [Hash, Array] Container
14
14
  def dotted_to_container(path, value, result = nil)
15
+ Aspera.assert_array_all(path, String)
15
16
  # Typed keys
16
- keys = path.split(OPTION_DOTTED_SEPARATOR).map{ |k| int_or_string(k)}
17
+ keys = path.map{ |k| int_or_string(k)}
17
18
  # Create, or re-use first level container
18
19
  current = (result ||= new_hash_or_array_from_key(keys.first))
19
20
  # walk the path, and create sub-containers if necessary
@@ -78,7 +79,7 @@ module Aspera
78
79
  to_insert = current.map{ |i| i['name']}
79
80
  # Array of Hashes with only 'name' and 'value' keys -> Hash of key/values
80
81
  elsif current.all?{ |i| i.is_a?(Hash) && i.keys.sort == %w[name value]}
81
- add_elements(path, current.each_with_object({}){ |i, h| h[i['name']] = i['value']})
82
+ add_elements(path, current.to_h{ |i| [i['name'], i['value']]})
82
83
  else
83
84
  add_elements(path, current.each_with_index.map{ |v, i| [i, v]})
84
85
  end
@@ -86,7 +87,7 @@ module Aspera
86
87
  to_insert = current
87
88
  end
88
89
  end
89
- result[path.map(&:to_s).join(OPTION_DOTTED_SEPARATOR)] = to_insert unless to_insert.nil?
90
+ result[path.join(SEPARATOR)] = to_insert unless to_insert.nil?
90
91
  end
91
92
  result
92
93
  end
@@ -101,8 +102,7 @@ module Aspera
101
102
  nil
102
103
  end
103
104
 
104
- # "."
105
- OPTION_DOTTED_SEPARATOR = '.'
106
- private_constant :OPTION_DOTTED_SEPARATOR
105
+ # Dot-path separator: `.`
106
+ SEPARATOR = '.'
107
107
  end
108
108
  end
data/lib/aspera/log.rb CHANGED
@@ -26,7 +26,7 @@ class Logger
26
26
  # Hash
27
27
  # key [Integer] Log level (e.g. 0 for DEBUG)
28
28
  # value [Symbol] Uppercase log level label (e.g. :DEBUG)
29
- SEVERITY_LABEL = Severity.constants.each_with_object({}){ |name, hash| hash[Severity.const_get(name)] = name}
29
+ SEVERITY_LABEL = Severity.constants.to_h{ |name| [Severity.const_get(name), name]}
30
30
 
31
31
  # Override
32
32
  # @param severity [Integer] Log severity as int
@@ -33,7 +33,7 @@ module Aspera
33
33
 
34
34
  # type: NOTE CAUTION WARNING IMPORTANT TIP INFO
35
35
  def admonition(lines, type: 'INFO')
36
- "> [!{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
36
+ "> [!#{type}]\n#{lines.map{ |l| "> #{l}"}.join("\n")}\n\n"
37
37
  end
38
38
 
39
39
  def code(lines, type: 'shell')
@@ -79,7 +79,7 @@ module Aspera
79
79
  end
80
80
 
81
81
  def current_items(persist_category)
82
- current_files(persist_category).each_with_object({}){ |i, h| h[File.basename(i, FILE_SUFFIX)] = File.read(i)}
82
+ current_files(persist_category).to_h{ |i| [File.basename(i, FILE_SUFFIX), File.read(i)]}
83
83
  end
84
84
 
85
85
  private