aspera-cli 4.21.2 → 4.23.0

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 (105) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/BUGS.md +1 -1
  4. data/CHANGELOG.md +402 -374
  5. data/CONTRIBUTING.md +6 -10
  6. data/README.md +1018 -687
  7. data/lib/aspera/agent/base.rb +9 -5
  8. data/lib/aspera/agent/connect.rb +30 -28
  9. data/lib/aspera/agent/desktop.rb +29 -25
  10. data/lib/aspera/agent/direct.rb +137 -125
  11. data/lib/aspera/agent/httpgw.rb +22 -26
  12. data/lib/aspera/agent/node.rb +14 -11
  13. data/lib/aspera/agent/transferd.rb +6 -2
  14. data/lib/aspera/api/aoc.rb +15 -18
  15. data/lib/aspera/api/cos_node.rb +1 -1
  16. data/lib/aspera/api/httpgw.rb +15 -7
  17. data/lib/aspera/api/node.rb +6 -4
  18. data/lib/aspera/ascmd.rb +17 -9
  19. data/lib/aspera/ascp/installation.rb +21 -19
  20. data/lib/aspera/ascp/management.rb +1 -1
  21. data/lib/aspera/assert.rb +14 -5
  22. data/lib/aspera/cli/error.rb +2 -2
  23. data/lib/aspera/cli/extended_value.rb +38 -19
  24. data/lib/aspera/cli/formatter.rb +48 -48
  25. data/lib/aspera/cli/hints.rb +10 -2
  26. data/lib/aspera/cli/main.rb +190 -168
  27. data/lib/aspera/cli/manager.rb +16 -16
  28. data/lib/aspera/cli/plugin.rb +24 -21
  29. data/lib/aspera/cli/plugin_factory.rb +1 -1
  30. data/lib/aspera/cli/plugins/alee.rb +1 -1
  31. data/lib/aspera/cli/plugins/aoc.rb +173 -126
  32. data/lib/aspera/cli/plugins/ats.rb +19 -17
  33. data/lib/aspera/cli/plugins/config.rb +87 -98
  34. data/lib/aspera/cli/plugins/console.rb +5 -3
  35. data/lib/aspera/cli/plugins/faspex.rb +39 -35
  36. data/lib/aspera/cli/plugins/faspex5.rb +104 -80
  37. data/lib/aspera/cli/plugins/faspio.rb +13 -1
  38. data/lib/aspera/cli/plugins/httpgw.rb +13 -1
  39. data/lib/aspera/cli/plugins/node.rb +336 -205
  40. data/lib/aspera/cli/plugins/orchestrator.rb +34 -40
  41. data/lib/aspera/cli/plugins/preview.rb +3 -3
  42. data/lib/aspera/cli/plugins/server.rb +7 -6
  43. data/lib/aspera/cli/plugins/shares.rb +5 -5
  44. data/lib/aspera/cli/sync_actions.rb +19 -18
  45. data/lib/aspera/cli/transfer_agent.rb +11 -15
  46. data/lib/aspera/cli/transfer_progress.rb +2 -2
  47. data/lib/aspera/cli/version.rb +1 -1
  48. data/lib/aspera/command_line_builder.rb +116 -95
  49. data/lib/aspera/coverage.rb +4 -3
  50. data/lib/aspera/data_repository.rb +1 -0
  51. data/lib/aspera/environment.rb +7 -6
  52. data/lib/aspera/faspex_gw.rb +14 -14
  53. data/lib/aspera/faspex_postproc.rb +7 -6
  54. data/lib/aspera/hash_ext.rb +2 -2
  55. data/lib/aspera/json_rpc.rb +1 -1
  56. data/lib/aspera/keychain/encrypted_hash.rb +47 -34
  57. data/lib/aspera/keychain/factory.rb +41 -0
  58. data/lib/aspera/keychain/hashicorp_vault.rb +71 -0
  59. data/lib/aspera/keychain/macos_security.rb +19 -11
  60. data/lib/aspera/log.rb +29 -34
  61. data/lib/aspera/nagios.rb +6 -6
  62. data/lib/aspera/node_simulator.rb +8 -8
  63. data/lib/aspera/oauth/base.rb +10 -6
  64. data/lib/aspera/oauth/factory.rb +6 -6
  65. data/lib/aspera/oauth/url_json.rb +6 -6
  66. data/lib/aspera/persistency_action_once.rb +6 -4
  67. data/lib/aspera/persistency_folder.rb +2 -2
  68. data/lib/aspera/preview/file_types.rb +40 -33
  69. data/lib/aspera/preview/generator.rb +1 -1
  70. data/lib/aspera/preview/options.rb +16 -16
  71. data/lib/aspera/preview/terminal.rb +3 -3
  72. data/lib/aspera/preview/utils.rb +11 -13
  73. data/lib/aspera/products/connect.rb +2 -1
  74. data/lib/aspera/products/desktop.rb +1 -1
  75. data/lib/aspera/products/transferd.rb +1 -1
  76. data/lib/aspera/proxy_auto_config.rb +2 -2
  77. data/lib/aspera/rest.rb +70 -50
  78. data/lib/aspera/rest_error_analyzer.rb +1 -0
  79. data/lib/aspera/rest_errors_aspera.rb +1 -1
  80. data/lib/aspera/secret_hider.rb +5 -5
  81. data/lib/aspera/ssh.rb +5 -5
  82. data/lib/aspera/temp_file_manager.rb +1 -0
  83. data/lib/aspera/timer_limiter.rb +7 -5
  84. data/lib/aspera/transfer/async_conf.schema.yaml +716 -0
  85. data/lib/aspera/transfer/convert.rb +29 -0
  86. data/lib/aspera/transfer/error_info.rb +66 -66
  87. data/lib/aspera/transfer/parameters.rb +13 -68
  88. data/lib/aspera/transfer/spec.rb +5 -6
  89. data/lib/aspera/transfer/spec.schema.yaml +753 -0
  90. data/lib/aspera/transfer/spec_doc.rb +62 -0
  91. data/lib/aspera/transfer/sync.rb +37 -76
  92. data/lib/aspera/transfer/sync_instance.schema.yaml +20 -0
  93. data/lib/aspera/transfer/sync_session.schema.yaml +86 -0
  94. data/lib/aspera/transfer/uri.rb +6 -6
  95. data/lib/aspera/uri_reader.rb +1 -1
  96. data/lib/aspera/web_auth.rb +1 -1
  97. data/lib/aspera/web_server_simple.rb +53 -44
  98. data.tar.gz.sig +0 -0
  99. metadata +38 -7
  100. metadata.gz.sig +0 -0
  101. data/examples/build_package.sh +0 -28
  102. data/examples/dascli +0 -30
  103. data/examples/get_proto_file.rb +0 -8
  104. data/examples/proxy.pac +0 -60
  105. data/lib/aspera/transfer/spec.yaml +0 -718
@@ -50,10 +50,13 @@ module Aspera
50
50
  'archived' => false,
51
51
  'has_content' => true,
52
52
  'received' => true,
53
- 'completed' => true}.freeze
53
+ 'completed' => true
54
+ }.freeze
55
+ PACKAGE_LIST_DEFAULT_FIELDS = %w[id name created_at files_completed bytes_transferred].freeze
54
56
  # options and parameters for Api::AoC.new
55
57
  OPTIONS_NEW = %i[url auth client_id client_secret scope redirect_uri private_key passphrase username password workspace].freeze
56
- private_constant :REDIRECT_LOCALHOST, :STD_AUTH_TYPES, :ADMIN_OBJECTS, :PACKAGE_RECEIVED_BASE_QUERY, :OPTIONS_NEW
58
+
59
+ private_constant :REDIRECT_LOCALHOST, :STD_AUTH_TYPES, :ADMIN_OBJECTS, :PACKAGE_RECEIVED_BASE_QUERY, :OPTIONS_NEW, :PACKAGE_LIST_DEFAULT_FIELDS
57
60
  class << self
58
61
  def application_name
59
62
  'Aspera on Cloud'
@@ -78,15 +81,16 @@ module Aspera
78
81
  }
79
82
  end
80
83
 
81
- # @param [String] url : url to check
84
+ # @param url [String] url to check
82
85
  # @return [Bool] true if private key is required for the url (i.e. no passcode)
83
86
  def private_key_required?(url)
84
87
  # pub link do not need private key
85
88
  return Api::AoC.link_info(url)[:token].nil?
86
89
  end
87
90
 
88
- # @param [Hash] env : options, formatter
89
- # @param [Hash] params : plugin_sym, instance_url
91
+ # @param object [Plugin] An instance of this class
92
+ # @param private_key_path [String] path to private key
93
+ # @param pub_key_pem [String] PEM of public key
90
94
  # @return [Hash] :preset_value, :test_args
91
95
  def wizard(object:, private_key_path: nil, pub_key_pem: nil)
92
96
  # set vars to look like object
@@ -121,7 +125,7 @@ module Aspera
121
125
  formatter.display_status('Please Login to your Aspera on Cloud instance.')
122
126
  formatter.display_status('Navigate to: 👤 → Account Settings → Profile → Public Key')
123
127
  formatter.display_status('Check or update the value to:'.red.blink)
124
- formatter.display_status(pub_key_pem)
128
+ formatter.display_status(pub_key_pem, hide_secrets: false)
125
129
  if !options.get_option(:test_mode)
126
130
  formatter.display_status('Once updated or validated, press enter.')
127
131
  Environment.instance.open_uri(instance_url)
@@ -188,22 +192,28 @@ module Aspera
188
192
  options.declare(:auth, 'OAuth type of authentication', values: STD_AUTH_TYPES, default: :jwt)
189
193
  options.declare(:client_id, 'OAuth API client identifier')
190
194
  options.declare(:client_secret, 'OAuth API client secret')
191
- options.declare(:scope, 'OAuth scope for AoC API calls', default: Api::AoC::SCOPE_FILES_USER)
195
+ options.declare(:scope, 'OAuth scope for AoC API calls')
192
196
  options.declare(:redirect_uri, 'OAuth API client redirect URI')
193
197
  options.declare(:private_key, 'OAuth JWT RSA private key PEM value (prefix file path with @file:)')
194
198
  options.declare(:passphrase, 'RSA private key passphrase', types: String)
195
199
  options.declare(:workspace, 'Name of workspace', types: [String, NilClass], default: Api::AoC::DEFAULT_WORKSPACE)
196
200
  options.declare(:new_user_option, 'New user creation option for unknown package recipients', types: Hash)
197
201
  options.declare(:validate_metadata, 'Validate shared inbox metadata', values: :bool, default: true)
202
+ options.declare(:package_folder, 'Field of package to use as folder name, or @none:', types: [String, NilClass])
198
203
  options.parse_options!
199
204
  # add node plugin options (for manual)
200
205
  Node.declare_options(options)
201
206
  end
202
207
 
203
- def api_from_options(new_base_path)
204
- create_values = {subpath: new_base_path, secret_finder: config}
208
+ def api_from_options(aoc_base_path)
209
+ create_values = OPTIONS_NEW.each_with_object({
210
+ subpath: aoc_base_path,
211
+ secret_finder: config}) do |i, m|
212
+ m[i] = options.get_option(i) unless options.get_option(i).nil?
213
+ end
214
+ create_values[:scope] = Api::AoC::SCOPE_FILES_USER if create_values[:scope].nil?
205
215
  # create an API object with the same options, but with a different subpath
206
- return Api::AoC.new(**OPTIONS_NEW.each_with_object(create_values) { |i, m|m[i] = options.get_option(i) unless options.get_option(i).nil?})
216
+ return Api::AoC.new(**create_values)
207
217
  rescue ArgumentError => e
208
218
  if (m = e.message.match(/missing keyword: :(.*)$/))
209
219
  raise Cli::Error, "Missing option: #{m[1]}"
@@ -216,13 +226,31 @@ module Aspera
216
226
  @cache_api_aoc = api_from_options(Api::AoC::API_V1)
217
227
  organization = @cache_api_aoc.read('organization')
218
228
  if organization['http_gateway_enabled'] && organization['http_gateway_server_url']
219
- transfer.httpgw_url_cb = lambda { organization['http_gateway_server_url'] }
229
+ transfer.httpgw_url_cb = lambda{organization['http_gateway_server_url']}
220
230
  # @cache_api_aoc.current_user_info['connect_disabled']
221
231
  end
222
232
  end
223
233
  return @cache_api_aoc
224
234
  end
225
235
 
236
+ # Generate or update Hash with workspace id and name (option), if not already set
237
+ # @param hash [Hash, Nil] set in provided hash
238
+ # @param string [Bool] true to set key as string, else as symbol
239
+ # @param name [Bool] include name
240
+ # @return [Hash] with key `workspace_[id,name]` (symbol or string) only if defined
241
+ def workspace_id_hash(hash: nil, string: false, name: false)
242
+ info = aoc_api.workspace
243
+ hash = {} if hash.nil?
244
+ fields = %i[id]
245
+ fields.push(:name) if name
246
+ fields.each do |i|
247
+ k = "workspace_#{i}"
248
+ k = k.to_sym unless string
249
+ hash[k] = info[i] unless info[i].nil? || hash.key?(k)
250
+ end
251
+ return hash
252
+ end
253
+
226
254
  # Get resource identifier from command line, either directly or from name.
227
255
  # @param resource_class_path url path for resource
228
256
  # @return identifier
@@ -240,7 +268,7 @@ module Aspera
240
268
 
241
269
  # Call block with same query using paging and response information
242
270
  # block must return a hash with :data and :http keys
243
- # @return [Hash] {data: , total: }
271
+ # @return [Hash] {items: , total: }
244
272
  def api_call_paging(base_query={})
245
273
  Aspera.assert_type(base_query, Hash){'query'}
246
274
  Aspera.assert(block_given?)
@@ -268,46 +296,59 @@ module Aspera
268
296
  item_list += add_items
269
297
  break if !max_items.nil? && item_list.count >= max_items
270
298
  break if !max_pages.nil? && page_count >= max_pages
299
+ formatter.long_operation_running("#{item_list.count} / #{total_count}") unless total_count.eql?(item_list.count.to_s)
271
300
  end
301
+ formatter.long_operation_terminated
272
302
  item_list = item_list[0..max_items - 1] if !max_items.nil? && item_list.count > max_items
273
- return {data: item_list, total: total_count}
303
+ return {items: item_list, total: total_count}
274
304
  end
275
305
 
276
306
  # read using the query and paging
277
307
  # @return [Hash] {data: , total: }
278
308
  def api_read_all(resource_class_path, base_query={})
279
309
  return api_call_paging(base_query) do |query|
280
- aoc_api.call(operation: 'GET', subpath: resource_class_path, headers: {'Accept' => 'application/json'}, query: query)
310
+ aoc_api.call(operation: 'GET', subpath: resource_class_path, headers: {'Accept' => Rest::MIME_JSON}, query: query)
281
311
  end
282
312
  end
283
313
 
284
- # list all entities, given additional, default and user's queries
314
+ # List all entities, given additional, default and user's queries
285
315
  # @param resource_class_path path to query on API
286
316
  # @param fields fields to display
287
317
  # @param base_query a query applied always
288
318
  # @param default_query default query unless overriden by user
319
+ # @param &block (Optional) calls block with user's or default query
289
320
  def result_list(resource_class_path, fields: nil, base_query: {}, default_query: {})
290
321
  Aspera.assert_type(base_query, Hash)
291
322
  Aspera.assert_type(default_query, Hash)
292
- user_query = query_read_delete(default: default_query)
293
- # caller may add specific modifications or checks
294
- yield(user_query) if block_given?
295
- return {type: :object_list, fields: fields}.merge(api_read_all(resource_class_path, base_query.merge(user_query).compact))
323
+ query = query_read_delete(default: default_query)
324
+ # caller may add specific modifications or checks to query
325
+ yield(query) if block_given?
326
+ result = api_read_all(resource_class_path, base_query.merge(query).compact)
327
+ return Main.result_object_list(result[:items], fields: fields, total: result[:total])
296
328
  end
297
329
 
330
+ # Translates `dropbox_name` to `dropbox_id` and fills current workspace_id
298
331
  def resolve_dropbox_name_default_ws_id(query)
299
332
  if query.key?('dropbox_name')
300
333
  # convenience: specify name instead of id
301
- raise 'not both dropbox_name and dropbox_id' if query.key?('dropbox_id')
334
+ raise 'Use field dropbox_name or dropbox_id, not both' if query.key?('dropbox_id')
302
335
  # TODO : craft a query that looks for dropbox only in current workspace
303
- query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query['dropbox_name'])['id']
304
- query.delete('dropbox_name')
336
+ query['dropbox_id'] = aoc_api.lookup_by_name('dropboxes', query.delete('dropbox_name'))['id']
305
337
  end
306
- query['workspace_id'] ||= aoc_api.workspace[:id] unless aoc_api.workspace[:id].eql?(:undefined)
338
+ workspace_id_hash(hash: query, string: true)
307
339
  # by default show dropbox packages only for dropboxes
308
340
  query['exclude_dropbox_packages'] = !query.key?('dropbox_id') unless query.key?('exclude_dropbox_packages')
309
341
  end
310
342
 
343
+ # @return [Hash] {items,total} with all packages according to combination of user's query and default query
344
+ def list_all_packages_with_query
345
+ query = query_read_delete(default: {})
346
+ Aspera.assert_type(query, Hash){'query'}
347
+ PACKAGE_RECEIVED_BASE_QUERY.each{ |k, v| query[k] = v unless query.key?(k)}
348
+ resolve_dropbox_name_default_ws_id(query)
349
+ return api_read_all('packages', query.compact)
350
+ end
351
+
311
352
  NODE4_EXT_COMMANDS = %i[transfer].concat(Node::COMMANDS_GEN4).freeze
312
353
  private_constant :NODE4_EXT_COMMANDS
313
354
 
@@ -316,9 +357,8 @@ module Aspera
316
357
  def execute_nodegen4_command(command_repo, node_id, file_id: nil, scope: nil)
317
358
  top_node_api = aoc_api.node_api_from(
318
359
  node_id: node_id,
319
- workspace_id: aoc_api.workspace[:id],
320
- workspace_name: aoc_api.workspace[:name],
321
- scope: scope
360
+ scope: scope,
361
+ **workspace_id_hash(name: true)
322
362
  )
323
363
  file_id = top_node_api.read("access_keys/#{top_node_api.app_info[:node_info]['access_key']}")['root_file_id'] if file_id.nil?
324
364
  node_plugin = Node.new(**init_params, api: top_node_api)
@@ -410,7 +450,9 @@ module Aspera
410
450
  default_fields.push('app_type', 'app_name', 'available', 'direct_authorizations_allowed', 'workspace_authorizations_allowed')
411
451
  when :client, :client_access_key, :dropbox, :group, :package, :saml_configuration, :workspace then default_fields.push('name')
412
452
  when :client_registration_token then default_fields.push('value', 'data.client_subject_scopes', 'created_at')
413
- when :contact then default_fields = %w[email name source_id source_type]
453
+ when :contact
454
+ default_fields = %w[source_type source_id name email]
455
+ default_query = {'include_only_user_personal_contacts' => true} if aoc_api.oauth.scope == Api::AoC::SCOPE_FILES_USER
414
456
  when :node then default_fields.push('name', 'host', 'access_key')
415
457
  when :operation then default_fields = nil
416
458
  when :short_link then default_fields.push('short_url', 'data.url_token_data.purpose')
@@ -422,8 +464,8 @@ module Aspera
422
464
  when :show
423
465
  object = aoc_api.read(resource_instance_path)
424
466
  # default: show all, but certificate
425
- fields = object.keys.reject{|k|k.eql?('certificate')}
426
- return { type: :single_object, data: object, fields: fields }
467
+ fields = object.keys.reject{ |k| k.eql?('certificate')}
468
+ return Main.result_single_object(object, fields: fields)
427
469
  when :modify
428
470
  changes = options.get_next_argument('properties', validation: Hash)
429
471
  return do_bulk_operation(command: command, descr: 'identifier', values: res_id) do |one_id|
@@ -453,8 +495,8 @@ module Aspera
453
495
  ADMIN_ACTIONS = %i[ats resource usage_reports analytics subscription auth_providers].concat(ADMIN_OBJECTS).freeze
454
496
 
455
497
  def execute_admin_action
456
- # upgrade scope to admin
457
- aoc_api.oauth.scope = Api::AoC::SCOPE_FILES_ADMIN
498
+ # default scope to admin
499
+ aoc_api.oauth.scope = Api::AoC::SCOPE_FILES_ADMIN if options.get_option(:scope).nil?
458
500
  command_admin = options.get_next_command(ADMIN_ACTIONS)
459
501
  case command_admin
460
502
  when :resource
@@ -529,7 +571,7 @@ module Aspera
529
571
  GRAPHQL
530
572
  # cspell:enable
531
573
  result = bss_graphql.create(nil, {query: graphql_query, variables: {organization_id: org['id']}})['data']
532
- return {type: :single_object, data: result['aoc']['bssSubscription']}
574
+ return Main.result_single_object(result['aoc']['bssSubscription'])
533
575
  when :usage
534
576
  # cspell:disable
535
577
  graphql_query = <<-GRAPHQL
@@ -576,7 +618,7 @@ module Aspera
576
618
  aggregate: aggregate,
577
619
  startDate: start_date,
578
620
  endDate: end_date}})['data']
579
- return {type: :single_object, data: result['aoc']}
621
+ return Main.result_single_object(result['aoc'])
580
622
  end
581
623
  when :ats
582
624
  ats_api = Rest.new(**aoc_api.params.deep_merge({
@@ -594,7 +636,7 @@ module Aspera
594
636
  when :application_events
595
637
  event_type = command_analytics.to_s
596
638
  events = analytics_api.read("organizations/#{aoc_api.current_user_info['organization_id']}/#{event_type}")[event_type]
597
- return {type: :object_list, data: events}
639
+ return Main.result_object_list(events)
598
640
  when :transfers
599
641
  event_type = command_analytics.to_s
600
642
  filter_resource = options.get_next_argument('resource', accept_list: %i[organizations users nodes])
@@ -635,25 +677,26 @@ module Aspera
635
677
  config.send_email_template(values: {ev: tr_event})
636
678
  end
637
679
  end
638
- return {type: :object_list, data: events}
680
+ return Main.result_object_list(events)
639
681
  end
640
682
  when :usage_reports
641
683
  aoc_api.context = :files
642
- return result_list('usage_reports', base_query: {workspace_id: aoc_api.workspace[:id]})
684
+ return result_list('usage_reports', base_query: workspace_id_hash)
643
685
  end
644
686
  end
645
687
 
646
688
  # Create a shared link for the given entity
647
- # @param shared_data [Hash] information for shared data
648
- # @param block [Proc] Optional: called on creation
649
- def short_link_command(shared_data, purpose_public:)
689
+ # @param purpose_public [Symbol]
690
+ # @param shared_data [Hash] information for shared data
691
+ # @param block [Proc] Optional: called on creation
692
+ def short_link_command(purpose_public:, **shared_data)
650
693
  link_type = options.get_next_argument('link type', accept_list: %i[public private])
651
694
  purpose_local = case link_type
652
695
  when :public
653
696
  case purpose_public
654
697
  when /package/ then 'send_package_to_dropbox'
655
698
  when /shared/ then 'token_auth_redirection'
656
- else raise 'error'
699
+ else Aspera.error_unexpected_value(purpose_public){'public link purpose'}
657
700
  end
658
701
  when :private then 'shared_folder_auth_link'
659
702
  else Aspera.error_unreachable_line
@@ -682,7 +725,7 @@ module Aspera
682
725
  result_create_short_link = aoc_api.create('short_links', creation_params)
683
726
  # public: Creation: permission on node
684
727
  yield(result_create_short_link['resource_id']) if block_given? && link_type.eql?(:public)
685
- return {type: :single_object, data: result_create_short_link}
728
+ return Main.result_single_object(result_create_short_link)
686
729
  when :list
687
730
  query = if link_type.eql?(:private)
688
731
  shared_data
@@ -718,6 +761,26 @@ module Aspera
718
761
  end
719
762
  end
720
763
 
764
+ # @return persistency object if option `once_only` is used.
765
+ def package_persistency
766
+ return nil unless options.get_option(:once_only, mandatory: true)
767
+ # TODO: add query info to id
768
+ PersistencyActionOnce.new(
769
+ manager: persistency,
770
+ data: [],
771
+ id: IdGenerator.from_list(
772
+ ['aoc_recv',
773
+ options.get_option(:url, mandatory: true),
774
+ aoc_api.workspace[:id]
775
+ ].concat(aoc_api.additional_persistence_ids)))
776
+ end
777
+
778
+ def reject_packages_from_persistency(all_packages, skip_ids_persistency)
779
+ return if skip_ids_persistency.nil?
780
+ skip_package = skip_ids_persistency.data.each_with_object({}){ |i, m| m[i] = true}
781
+ all_packages.reject!{ |pkg| skip_package[pkg['id']]}
782
+ end
783
+
721
784
  # must be public
722
785
  ACTIONS = %i[reminder servers bearer_token organization tier_restrictions user packages files admin automation gateway].freeze
723
786
 
@@ -739,29 +802,31 @@ module Aspera
739
802
  Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").create('organization_reminders', {email: user_email})
740
803
  return Main.result_status("List of organizations user is member of, has been sent by e-mail to #{user_email}")
741
804
  when :servers
742
- return {type: :object_list, data: Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").read('servers')}
805
+ return Main.result_object_list(Rest.new(base_url: "#{Api::AoC.api_base_url}/#{Api::AoC::API_V1}").read('servers'))
743
806
  when :bearer_token
744
- return {type: :text, data: aoc_api.oauth.authorization}
807
+ return Main.result_text(aoc_api.oauth.authorization)
745
808
  when :organization
746
- return { type: :single_object, data: aoc_api.read('organization') }
809
+ return Main.result_single_object(aoc_api.read('organization'))
747
810
  when :tier_restrictions
748
- return { type: :single_object, data: aoc_api.read('tier_restrictions') }
811
+ return Main.result_single_object(aoc_api.read('tier_restrictions'))
749
812
  when :user
750
- case options.get_next_command(%i[workspaces profile preferences])
813
+ case options.get_next_command(%i[workspaces profile preferences contacts])
814
+ when :contacts
815
+ return execute_resource_action(:contact)
751
816
  # when :settings
752
- # return {type: :object_list,data: aoc_api.read('client_settings/')}
817
+ # return Main.result_object_list(aoc_api.read('client_settings/'))
753
818
  when :workspaces
754
819
  case options.get_next_command(%i[list current])
755
820
  when :list
756
821
  return result_list('workspaces', fields: %w[id name])
757
822
  when :current
758
823
  aoc_api.context = :files
759
- return { type: :single_object, data: aoc_api.read("workspaces/#{aoc_api.workspace[:id]}") }
824
+ return Main.result_single_object(aoc_api.workspace)
760
825
  end
761
826
  when :profile
762
827
  case options.get_next_command(%i[show modify])
763
828
  when :show
764
- return { type: :single_object, data: aoc_api.current_user_info(exception: true) }
829
+ return Main.result_single_object(aoc_api.current_user_info(exception: true))
765
830
  when :modify
766
831
  aoc_api.update("users/#{aoc_api.current_user_info(exception: true)['id']}", options.get_next_argument('properties', validation: Hash))
767
832
  return Main.result_status('modified')
@@ -770,7 +835,7 @@ module Aspera
770
835
  user_preferences_res = "users/#{aoc_api.current_user_info(exception: true)['id']}/user_interaction_preferences"
771
836
  case options.get_next_command(%i[show modify])
772
837
  when :show
773
- return { type: :single_object, data: aoc_api.read(user_preferences_res) }
838
+ return Main.result_single_object(aoc_api.read(user_preferences_res))
774
839
  when :modify
775
840
  aoc_api.update(user_preferences_res, options.get_next_argument('properties', validation: Hash))
776
841
  return Main.result_status('modified')
@@ -783,26 +848,24 @@ module Aspera
783
848
  case options.get_next_command(%i[list show short_link])
784
849
  when :list
785
850
  default_query = {'embed[]' => 'dropbox', 'aggregate_permissions_by_dropbox' => true, 'sort' => 'dropbox_name'}
786
- default_query['workspace_id'] = aoc_api.workspace[:id] unless aoc_api.workspace[:id].eql?(:undefined)
851
+ workspace_id_hash(hash: default_query, string: true)
787
852
  return result_list('dropbox_memberships', fields: %w[dropbox_id dropbox.name], default_query: default_query)
788
853
  when :show
789
- return {type: :single_object, data: aoc_api.read(get_resource_path_from_args('dropboxes'))}
854
+ return Main.result_single_object(aoc_api.read(get_resource_path_from_args('dropboxes')))
790
855
  when :short_link
791
856
  return short_link_command(
792
- {
793
- workspace_id: aoc_api.workspace[:id],
794
- dropbox_id: get_resource_id_from_args('dropboxes'),
795
- name: ''
796
- },
797
- purpose_public: 'send_package_to_dropbox')
857
+ purpose_public: 'send_package_to_dropbox',
858
+ dropbox_id: get_resource_id_from_args('dropboxes'),
859
+ name: '',
860
+ **workspace_id_hash
861
+ )
798
862
  end
799
863
  when :send
800
864
  package_data = value_create_modify(command: package_command)
801
865
  new_user_option = options.get_option(:new_user_option)
802
866
  option_validate = options.get_option(:validate_metadata)
803
- # works for both normal usr auth and link auth
804
- package_data['workspace_id'] ||= aoc_api.workspace[:id]
805
-
867
+ # works for both normal user auth and link auth
868
+ workspace_id_hash(hash: package_data, string: true) unless package_data.key?('workspace_id')
806
869
  if !aoc_api.public_link.nil?
807
870
  aoc_api.assert_public_link_types(%w[send_package_to_user send_package_to_dropbox])
808
871
  box_type = aoc_api.public_link['purpose'].split('_').last
@@ -815,88 +878,73 @@ module Aspera
815
878
  created_package = aoc_api.create_package_simple(package_data, option_validate, new_user_option)
816
879
  Main.result_transfer(transfer.start(created_package[:spec], rest_token: created_package[:node]))
817
880
  # return all info on package (especially package id)
818
- return { type: :single_object, data: created_package[:info]}
881
+ return Main.result_single_object(created_package[:info])
819
882
  when :receive
820
883
  ids_to_download = nil
821
884
  if !aoc_api.public_link.nil?
822
885
  aoc_api.assert_public_link_types(['view_received_package'])
823
- # set the package id, it will
886
+ # set the package id from link
824
887
  ids_to_download = aoc_api.public_link['data']['package_id']
825
888
  end
826
889
  # get from command line unless it was a public link
827
890
  ids_to_download ||= instance_identifier
828
- skip_ids_data = []
829
- skip_ids_persistency = nil
830
- if options.get_option(:once_only, mandatory: true)
831
- # TODO: add query info to id
832
- skip_ids_persistency = PersistencyActionOnce.new(
833
- manager: persistency,
834
- data: skip_ids_data,
835
- id: IdGenerator.from_list(
836
- ['aoc_recv',
837
- options.get_option(:url, mandatory: true),
838
- aoc_api.workspace[:id]
839
- ].concat(aoc_api.additional_persistence_ids)))
840
- end
891
+ skip_ids_persistency = package_persistency
841
892
  case ids_to_download
842
893
  when SpecialValues::ALL, SpecialValues::INIT
843
- query = query_read_delete(default: PACKAGE_RECEIVED_BASE_QUERY)
844
- Aspera.assert_type(query, Hash){'query'}
845
- resolve_dropbox_name_default_ws_id(query)
846
- # remove from list the ones already downloaded
847
- all_ids = api_read_all('packages', query)[:data].map{|e|e['id']}
894
+ all_packages = list_all_packages_with_query[:items]
848
895
  if ids_to_download.eql?(SpecialValues::INIT)
849
- Aspera.assert(skip_ids_persistency){'Only with option once_only'}
850
- skip_ids_persistency.data.clear.concat(all_ids)
896
+ Aspera.assert(skip_ids_persistency){'INIT requires option once_only'}
897
+ skip_ids_persistency.data.clear.concat(all_packages.map{ |e| e['id']})
851
898
  skip_ids_persistency.save
852
899
  return Main.result_status("Initialized skip for #{skip_ids_persistency.data.count} package(s)")
853
900
  end
854
- # array here
855
- ids_to_download = all_ids.reject{|id|skip_ids_data.include?(id)}
901
+ # remove from list the ones already downloaded
902
+ reject_packages_from_persistency(all_packages, skip_ids_persistency)
903
+ ids_to_download = all_packages.map{ |e| e['id']}
904
+ formatter.display_status("Found #{ids_to_download.length} package(s).")
856
905
  else
906
+ # single id to array
857
907
  ids_to_download = [ids_to_download] unless ids_to_download.is_a?(Array)
858
908
  end
859
- file_list =
860
- begin
861
- transfer.source_list.map{|i|{'source'=>i}}
862
- rescue Cli::BadArgument
863
- [{'source' => '.'}]
864
- end
865
- # list here
909
+ # download all files, or specified list only
910
+ ts_paths = transfer.ts_source_paths(default: ['.'])
911
+ per_package_field = options.get_option(:package_folder)
912
+ destination_folder = transfer.destination_folder(Transfer::Spec::DIRECTION_RECEIVE)
866
913
  result_transfer = []
867
- formatter.display_status("found #{ids_to_download.length} package(s).")
868
914
  ids_to_download.each do |package_id|
869
915
  package_info = aoc_api.read("packages/#{package_id}")
870
- formatter.display_status("downloading package: [#{package_info['id']}] #{package_info['name']}")
871
916
  package_node_api = aoc_api.node_api_from(
872
917
  node_id: package_info['node_id'],
873
- workspace_id: aoc_api.workspace[:id],
874
- workspace_name: aoc_api.workspace[:name],
875
- package_info: package_info)
918
+ package_info: package_info,
919
+ **workspace_id_hash(name: true))
920
+ transfer_spec = package_node_api.transfer_spec_gen4(
921
+ package_info['contents_file_id'],
922
+ Transfer::Spec::DIRECTION_RECEIVE,
923
+ {'paths'=> ts_paths})
924
+ transfer.option_transfer_spec['destination_root'] = File.join(destination_folder, package_info[per_package_field]) unless per_package_field.nil?
925
+ formatter.display_status(%Q{Downloading package: [#{package_info['id']}] "#{package_info['name']}" to [#{transfer.option_transfer_spec['destination_root']}]})
876
926
  statuses = transfer.start(
877
- package_node_api.transfer_spec_gen4(
878
- package_info['contents_file_id'],
879
- Transfer::Spec::DIRECTION_RECEIVE,
880
- {'paths'=> file_list}),
927
+ transfer_spec,
881
928
  rest_token: package_node_api)
882
929
  result_transfer.push({'package' => package_id, Main::STATUS_FIELD => statuses})
883
930
  # update skip list only if all transfer sessions completed
884
- if TransferAgent.session_status(statuses).eql?(:success)
885
- skip_ids_data.push(package_id)
886
- skip_ids_persistency&.save
931
+ if skip_ids_persistency && TransferAgent.session_status(statuses).eql?(:success)
932
+ skip_ids_persistency.data.push(package_id)
933
+ skip_ids_persistency.save
887
934
  end
888
935
  end
889
936
  return Main.result_transfer_multiple(result_transfer)
890
937
  when :show
891
938
  package_id = instance_identifier
892
939
  package_info = aoc_api.read("packages/#{package_id}")
893
- return { type: :single_object, data: package_info }
940
+ return Main.result_single_object(package_info)
894
941
  when :list
895
- display_fields = %w[id name bytes_transferred]
896
- display_fields.push('workspace_id') if aoc_api.workspace[:id].eql?(:undefined)
897
- return result_list('packages', fields: display_fields, base_query: PACKAGE_RECEIVED_BASE_QUERY) do |query|
898
- resolve_dropbox_name_default_ws_id(query)
899
- end
942
+ result = list_all_packages_with_query
943
+ skip_ids_persistency = package_persistency
944
+ reject_packages_from_persistency(result[:items], skip_ids_persistency)
945
+ display_fields = PACKAGE_LIST_DEFAULT_FIELDS
946
+ display_fields += ['workspace_id'] if aoc_api.workspace[:id].nil?
947
+ return Main.result_object_list(result[:items], fields: display_fields, total: result[:total])
900
948
  when :delete
901
949
  return do_bulk_operation(command: package_command, descr: 'identifier', values: instance_identifier) do |id|
902
950
  Aspera.assert_values(id.class, [String, Integer]){'identifier'}
@@ -915,16 +963,15 @@ module Aspera
915
963
  when :short_link
916
964
  folder_dest = options.get_next_argument('path', validation: String)
917
965
  home_node_api = aoc_api.node_api_from(
918
- node_id: aoc_api.home[:node_id],
919
- workspace_id: aoc_api.workspace[:id],
920
- workspace_name: aoc_api.workspace[:name])
966
+ node_id: aoc_api.home[:node_id],
967
+ **workspace_id_hash(name: true))
921
968
  shared_apfid = home_node_api.resolve_api_fid(aoc_api.home[:file_id], folder_dest)
922
969
  return short_link_command(
923
- {
924
- workspace_id: aoc_api.workspace[:id],
925
- node_id: shared_apfid[:api].app_info[:node_info]['id'],
926
- file_id: shared_apfid[:file_id]
927
- }, purpose_public: 'view_shared_file') do |resource_id|
970
+ purpose_public: 'view_shared_file',
971
+ node_id: shared_apfid[:api].app_info[:node_info]['id'],
972
+ file_id: shared_apfid[:file_id],
973
+ **workspace_id_hash
974
+ ) do |resource_id|
928
975
  # TODO: merge with node permissions ?
929
976
  # TODO: access level as arg
930
977
  access_levels = Api::Node::ACCESS_LEVELS # ['delete','list','mkdir','preview','read','rename','write']
@@ -936,13 +983,12 @@ module Aspera
936
983
  'tags' => {
937
984
  # TODO: really just here ? not in tags.aspera.files.workspace ?
938
985
  'url_token' => true,
939
- 'workspace_id' => aoc_api.workspace[:id],
940
- 'workspace_name' => aoc_api.workspace[:name],
941
986
  'folder_name' => File.basename(folder_dest),
942
987
  'created_by_name' => aoc_api.current_user_info['name'],
943
988
  'created_by_email' => aoc_api.current_user_info['email'],
944
989
  'access_key' => shared_apfid[:api].app_info[:node_info]['access_key'],
945
- 'node' => shared_apfid[:api].app_info[:node_info]['host']
990
+ 'node' => shared_apfid[:api].app_info[:node_info]['host'],
991
+ **workspace_id_hash(string: true, name: true)
946
992
  }
947
993
  }
948
994
  created_data = shared_apfid[:api].create('permissions', perm_data)
@@ -965,7 +1011,7 @@ module Aspera
965
1011
  when :launch
966
1012
  wf_id = instance_identifier
967
1013
  data = automation_api.create("workflows/#{wf_id}/launch", {})
968
- return {type: :single_object, data: data}
1014
+ return Main.result_single_object(data)
969
1015
  when :action
970
1016
  # TODO: not complete
971
1017
  wf_id = instance_identifier
@@ -976,16 +1022,17 @@ module Aspera
976
1022
  action = automation_api.create('actions', {'step_id' => step['id'], 'type' => 'manual'})
977
1023
  automation_api.update("steps/#{step['id']}", {'action_order' => [action['id']]})
978
1024
  wf = automation_api.read("workflows/#{wf_id}")
979
- return {type: :single_object, data: wf}
1025
+ return Main.result_single_object(wf)
980
1026
  end
981
1027
  end
982
1028
  when :admin
983
1029
  return execute_admin_action
984
1030
  when :gateway
985
1031
  require 'aspera/faspex_gw'
986
- url = value_create_modify(command: command, type: String)
987
- uri = URI.parse(url)
988
- server = WebServerSimple.new(uri)
1032
+ parameters = value_create_modify(command: command, default: {}).symbolize_keys
1033
+ uri = URI.parse(parameters.delete(:url){WebServerSimple::DEFAULT_URL})
1034
+ server = WebServerSimple.new(uri, **parameters.slice(*WebServerSimple::PARAMS))
1035
+ Aspera.assert(parameters.except(*WebServerSimple::PARAMS).empty?)
989
1036
  aoc_api.context = :files
990
1037
  server.mount(uri.path, Faspex4GWServlet, aoc_api, aoc_api.workspace[:id])
991
1038
  server.start