aspera-cli 4.22.0 → 4.24.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 (114) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +405 -364
  4. data/CONTRIBUTING.md +86 -29
  5. data/README.md +1856 -961
  6. data/bin/ascli +2 -1
  7. data/bin/asession +4 -4
  8. data/lib/aspera/agent/base.rb +4 -0
  9. data/lib/aspera/agent/connect.rb +20 -18
  10. data/lib/aspera/agent/desktop.rb +14 -11
  11. data/lib/aspera/agent/direct.rb +39 -31
  12. data/lib/aspera/agent/httpgw.rb +2 -2
  13. data/lib/aspera/agent/node.rb +9 -11
  14. data/lib/aspera/agent/transferd.rb +18 -11
  15. data/lib/aspera/api/aoc.rb +53 -43
  16. data/lib/aspera/api/cos_node.rb +7 -5
  17. data/lib/aspera/api/httpgw.rb +23 -22
  18. data/lib/aspera/api/node.rb +104 -22
  19. data/lib/aspera/ascmd.rb +35 -21
  20. data/lib/aspera/ascp/installation.rb +43 -43
  21. data/lib/aspera/ascp/management.rb +5 -4
  22. data/lib/aspera/assert.rb +55 -24
  23. data/lib/aspera/cli/basic_auth_plugin.rb +8 -7
  24. data/lib/aspera/cli/error.rb +1 -1
  25. data/lib/aspera/cli/extended_value.rb +28 -29
  26. data/lib/aspera/cli/formatter.rb +191 -168
  27. data/lib/aspera/cli/hints.rb +38 -4
  28. data/lib/aspera/cli/main.rb +139 -108
  29. data/lib/aspera/cli/manager.rb +51 -31
  30. data/lib/aspera/cli/plugin.rb +149 -78
  31. data/lib/aspera/cli/plugin_factory.rb +2 -2
  32. data/lib/aspera/cli/plugins/aoc.rb +217 -88
  33. data/lib/aspera/cli/plugins/ats.rb +15 -13
  34. data/lib/aspera/cli/plugins/config.rb +105 -227
  35. data/lib/aspera/cli/plugins/console.rb +49 -18
  36. data/lib/aspera/cli/plugins/cos.rb +4 -4
  37. data/lib/aspera/cli/plugins/faspex.rb +45 -51
  38. data/lib/aspera/cli/plugins/faspex5.rb +162 -163
  39. data/lib/aspera/cli/plugins/faspio.rb +6 -5
  40. data/lib/aspera/cli/plugins/httpgw.rb +2 -2
  41. data/lib/aspera/cli/plugins/node.rb +233 -247
  42. data/lib/aspera/cli/plugins/orchestrator.rb +10 -14
  43. data/lib/aspera/cli/plugins/preview.rb +26 -29
  44. data/lib/aspera/cli/plugins/server.rb +29 -28
  45. data/lib/aspera/cli/plugins/shares.rb +40 -28
  46. data/lib/aspera/cli/sync_actions.rb +101 -80
  47. data/lib/aspera/cli/transfer_agent.rb +55 -58
  48. data/lib/aspera/cli/transfer_progress.rb +29 -20
  49. data/lib/aspera/cli/version.rb +1 -1
  50. data/lib/aspera/cli/wizard.rb +160 -0
  51. data/lib/aspera/colors.rb +13 -8
  52. data/lib/aspera/command_line_builder.rb +28 -22
  53. data/lib/aspera/command_line_converter.rb +31 -0
  54. data/lib/aspera/data_repository.rb +1 -0
  55. data/lib/aspera/environment.rb +144 -100
  56. data/lib/aspera/faspex_gw.rb +1 -1
  57. data/lib/aspera/faspex_postproc.rb +3 -2
  58. data/lib/aspera/hash_ext.rb +1 -1
  59. data/lib/aspera/id_generator.rb +10 -10
  60. data/lib/aspera/keychain/base.rb +18 -0
  61. data/lib/aspera/keychain/encrypted_hash.rb +6 -12
  62. data/lib/aspera/keychain/factory.rb +9 -3
  63. data/lib/aspera/keychain/hashicorp_vault.rb +9 -6
  64. data/lib/aspera/keychain/macos_security.rb +13 -13
  65. data/lib/aspera/log.rb +70 -20
  66. data/lib/aspera/nagios.rb +5 -6
  67. data/lib/aspera/node_simulator.rb +12 -7
  68. data/lib/aspera/oauth/base.rb +6 -2
  69. data/lib/aspera/oauth/factory.rb +25 -18
  70. data/lib/aspera/oauth/jwt.rb +13 -1
  71. data/lib/aspera/oauth/url_json.rb +3 -3
  72. data/lib/aspera/oauth/web.rb +5 -3
  73. data/lib/aspera/persistency_folder.rb +2 -2
  74. data/lib/aspera/preview/file_types.rb +43 -35
  75. data/lib/aspera/preview/generator.rb +26 -13
  76. data/lib/aspera/preview/terminal.rb +10 -7
  77. data/lib/aspera/preview/utils.rb +11 -9
  78. data/lib/aspera/products/connect.rb +2 -1
  79. data/lib/aspera/products/desktop.rb +1 -1
  80. data/lib/aspera/products/other.rb +2 -2
  81. data/lib/aspera/products/transferd.rb +8 -6
  82. data/lib/aspera/proxy_auto_config.rb +1 -1
  83. data/lib/aspera/rest.rb +46 -28
  84. data/lib/aspera/rest_call_error.rb +1 -1
  85. data/lib/aspera/rest_error_analyzer.rb +1 -0
  86. data/lib/aspera/resumer.rb +1 -1
  87. data/lib/aspera/secret_hider.rb +46 -40
  88. data/lib/aspera/ssh.rb +14 -4
  89. data/lib/aspera/sync/args.schema.yaml +102 -0
  90. data/lib/aspera/sync/conf.schema.yaml +701 -0
  91. data/lib/aspera/sync/database.rb +83 -0
  92. data/lib/aspera/{transfer/sync.rb → sync/operations.rb} +145 -68
  93. data/lib/aspera/temp_file_manager.rb +4 -2
  94. data/lib/aspera/timer_limiter.rb +7 -5
  95. data/lib/aspera/transfer/error.rb +1 -1
  96. data/lib/aspera/transfer/error_info.rb +1 -2
  97. data/lib/aspera/transfer/faux_file.rb +11 -10
  98. data/lib/aspera/transfer/parameters.rb +6 -5
  99. data/lib/aspera/transfer/spec.rb +15 -1
  100. data/lib/aspera/transfer/spec.schema.yaml +316 -293
  101. data/lib/aspera/transfer/spec_doc.rb +34 -16
  102. data/lib/aspera/transfer/uri.rb +5 -5
  103. data/lib/aspera/uri_reader.rb +14 -10
  104. data/lib/aspera/web_auth.rb +2 -2
  105. data/lib/aspera/web_server_simple.rb +2 -2
  106. data.tar.gz.sig +0 -0
  107. metadata +15 -15
  108. metadata.gz.sig +0 -0
  109. data/examples/dascli +0 -30
  110. data/examples/get_proto_file.rb +0 -8
  111. data/examples/proxy.pac +0 -60
  112. data/lib/aspera/transfer/convert.rb +0 -29
  113. data/lib/aspera/transfer/sync_instance.schema.yaml +0 -13
  114. data/lib/aspera/transfer/sync_session.schema.yaml +0 -79
@@ -34,6 +34,8 @@ module Aspera
34
34
  # types of events for shared folder creation
35
35
  # Node events: permission.created permission.modified permission.deleted
36
36
  PERMISSIONS_CREATED = ['permission.created'].freeze
37
+ # Special name when creating workspace shared folders
38
+ ID_AK_ADMIN = 'ASPERA_ACCESS_KEY_ADMIN'
37
39
 
38
40
  private_constant :MAX_AOC_URL_REDIRECT,
39
41
  :CLIENT_ID_PREFIX,
@@ -43,7 +45,8 @@ module Aspera
43
45
  :JWT_AUDIENCE,
44
46
  :OAUTH_API_SUBPATH,
45
47
  :USER_INFO_FIELDS_MIN,
46
- :PERMISSIONS_CREATED
48
+ :PERMISSIONS_CREATED,
49
+ :ID_AK_ADMIN
47
50
 
48
51
  # various API scopes supported
49
52
  SCOPE_FILES_SELF = 'self'
@@ -58,7 +61,7 @@ module Aspera
58
61
  # class static methods
59
62
  class << self
60
63
  # strings /Applications/Aspera\ Drive.app/Contents/MacOS/AsperaDrive|grep -E '.{100}==$'|base64 --decode
61
- def get_client_info(client_name=nil)
64
+ def get_client_info(client_name = nil)
62
65
  client_key = client_name.nil? ? GLOBAL_CLIENT_APPS.first : client_name.to_sym
63
66
  return client_key, DataRepository.instance.item(client_key)
64
67
  end
@@ -79,23 +82,25 @@ module Aspera
79
82
  end
80
83
 
81
84
  def saas_url?(url)
82
- url.include?(SAAS_DOMAIN_PROD)
85
+ URI.parse(url).host&.end_with?(".#{SAAS_DOMAIN_PROD}")
86
+ rescue URI::InvalidURIError
87
+ false
83
88
  end
84
89
 
85
90
  # @param url [String] URL of AoC public link
86
91
  # @return [Hash] information about public link, or nil if not a public link
87
92
  def link_info(url)
88
93
  final_uri = Rest.new(base_url: url, redirect_max: MAX_AOC_URL_REDIRECT).call(operation: 'GET')[:http].uri
89
- Log.log.trace1{Log.dump(:final_uri, final_uri)}
94
+ Log.dump(:final_uri, final_uri, level: :trace1)
90
95
  org_domain = split_org_domain(final_uri)
91
96
  if (m = final_uri.path.match(%r{/oauth2/([^/]+)/login$}))
92
97
  org_domain[:organization] = m[1] if org_domain[:organization].nil?
93
98
  else
94
99
  Log.log.debug{"path=#{final_uri.path} does not end with /login"}
95
100
  end
96
- raise 'AoC shall redirect to login page with a query' if final_uri.query.nil?
101
+ raise Error, 'AoC shall redirect to login page with a query' if final_uri.query.nil?
97
102
  query = Rest.query_to_h(final_uri.query)
98
- Log.log.trace1{Log.dump(:query, query)}
103
+ Log.dump(:query, query, level: :trace1)
99
104
  # is that a public link ?
100
105
  if query.key?('token')
101
106
  Log.log.warn{"Unknown pub link path: #{final_uri.path}"} unless PUBLIC_LINK_PATHS.include?(final_uri.path)
@@ -106,7 +111,7 @@ module Aspera
106
111
  token: query['token']
107
112
  }
108
113
  end
109
- if query['state']
114
+ if query.key?('state')
110
115
  # can be a private link
111
116
  state_uri = URI.parse(query['state'])
112
117
  if state_uri.query && query['redirect_uri']
@@ -129,7 +134,7 @@ module Aspera
129
134
  end
130
135
  end
131
136
  end
132
- Log.log.debug{Log.dump(:org_domain, org_domain)}
137
+ Log.dump(:org_domain, org_domain)
133
138
  return {
134
139
  instance_domain: org_domain[:domain],
135
140
  organization: org_domain[:organization]
@@ -163,7 +168,7 @@ module Aspera
163
168
  }
164
169
  # analyze type of url
165
170
  url_info = AoC.link_info(url)
166
- Log.log.debug{Log.dump(:url_info, url_info)}
171
+ Log.dump(:url_info, url_info)
167
172
  @private_link = url_info[:private_link]
168
173
  auth_params[:grant_method] = if url_info.key?(:token)
169
174
  :url_json
@@ -209,7 +214,7 @@ module Aspera
209
214
  end
210
215
 
211
216
  def public_link
212
- return nil unless auth_params[:grant_method].eql?(:url_json)
217
+ return unless auth_params[:grant_method].eql?(:url_json)
213
218
  return @cache_url_token_info unless @cache_url_token_info.nil?
214
219
  # TODO: can there be several in list ?
215
220
  @cache_url_token_info = read('url_tokens').first
@@ -242,12 +247,12 @@ module Aspera
242
247
  end
243
248
 
244
249
  def workspace
245
- raise 'internal error: AoC workspace context is not set' if @workspace_info.nil?
250
+ Aspera.assert(!@workspace_info.nil?){'AoC workspace context is not set'}
246
251
  @workspace_info
247
252
  end
248
253
 
249
254
  def home
250
- raise 'internal error: AoC home context is not set' if @home_info.nil?
255
+ Aspera.assert(!@home_info.nil?){'AoC home context is not set'}
251
256
  @home_info
252
257
  end
253
258
 
@@ -291,8 +296,8 @@ module Aspera
291
296
  name: ws_info['name']
292
297
  }
293
298
  end
294
- Log.log.debug{Log.dump(:context, @workspace_info)}
295
- return nil unless application.eql?(:files)
299
+ Log.dump(:context, @workspace_info)
300
+ return unless application.eql?(:files)
296
301
  @home_info =
297
302
  if !public_link.nil?
298
303
  assert_public_link_types(['view_shared_file'])
@@ -319,7 +324,7 @@ module Aspera
319
324
  }
320
325
  end
321
326
  raise "Cannot get user's home node id, check your default workspace or specify one" if @home_info[:node_id].to_s.empty?
322
- Log.log.debug{Log.dump(:context, @home_info)}
327
+ Log.dump(:context, @home_info)
323
328
  end
324
329
 
325
330
  # @param node_id [String] identifier of node in AoC
@@ -331,9 +336,7 @@ module Aspera
331
336
  def node_api_from(node_id:, workspace_id: nil, workspace_name: nil, scope: Node::SCOPE_USER, package_info: nil)
332
337
  Aspera.assert_type(node_id, String)
333
338
  node_info = read("nodes/#{node_id}")
334
- if workspace_name.nil? && !workspace_id.nil?
335
- workspace_name = read("workspaces/#{workspace_id}")['name']
336
- end
339
+ workspace_name = read("workspaces/#{workspace_id}")['name'] if workspace_name.nil? && !workspace_id.nil?
337
340
  app_info = {
338
341
  api: self, # for callback
339
342
  app: package_info.nil? ? FILES_APP : PACKAGES_APP,
@@ -342,7 +345,7 @@ module Aspera
342
345
  workspace_name: workspace_name
343
346
  }
344
347
  if PACKAGES_APP.eql?(app_info[:app])
345
- raise 'package info required' if package_info.nil?
348
+ Aspera.assert(!package_info.nil?){'package info required'}
346
349
  app_info[:package_id] = package_info['id']
347
350
  app_info[:package_name] = package_info['name']
348
351
  end
@@ -380,7 +383,7 @@ module Aspera
380
383
  Aspera.assert(pkg_data.key?('metadata')){"package requires metadata: #{meta_schema}"}
381
384
  pkg_meta = pkg_data['metadata']
382
385
  Aspera.assert_type(pkg_meta, Array){'metadata'}
383
- Log.log.debug{Log.dump(:metadata, pkg_meta)}
386
+ Log.dump(:metadata, pkg_meta)
384
387
  pkg_meta.each do |field|
385
388
  Aspera.assert_type(field, Hash){'metadata field'}
386
389
  Aspera.assert(field.key?('name')){'metadata field must have name'}
@@ -440,7 +443,7 @@ module Aspera
440
443
  end
441
444
  # replace with resolved elements
442
445
  package_data[recipient_list_field] = resolved_list
443
- return nil
446
+ return
444
447
  end
445
448
 
446
449
  # CLI allows simplified format for metadata: transform if necessary for API
@@ -458,7 +461,7 @@ module Aspera
458
461
  pkg_data['metadata'] = api_meta
459
462
  else Aspera.error_unexpected_value(pkg_meta.class)
460
463
  end
461
- return nil
464
+ return
462
465
  end
463
466
 
464
467
  # create a package
@@ -477,18 +480,24 @@ module Aspera
477
480
 
478
481
  validate_metadata(package_data) if validate_meta
479
482
 
483
+ # tell AoC what to expect in package: 1 transfer (can also be done after transfer)
484
+ # TODO: if multi session was used we should probably tell
485
+ # also, currently no "multi-source" , i.e. only from client-side files, unless "node" agent is used
486
+ # `single_source` is required to allow web UI to ask for CSEAR password on download, see API doc
487
+ package_data.merge!({
488
+ 'single_source' => true,
489
+ 'sent' => true,
490
+ 'transfers_expected' => 1
491
+ })
492
+
480
493
  # create a new package container
481
494
  created_package = create('packages', package_data)
482
495
 
483
496
  package_node_api = node_api_from(
484
497
  node_id: created_package['node_id'],
485
498
  workspace_id: created_package['workspace_id'],
486
- package_info: created_package)
487
-
488
- # tell AoC what to expect in package: 1 transfer (can also be done after transfer)
489
- # TODO: if multi session was used we should probably tell
490
- # also, currently no "multi-source" , i.e. only from client-side files, unless "node" agent is used
491
- update("packages/#{created_package['id']}", {'sent' => true, 'transfers_expected' => 1})
499
+ package_info: created_package
500
+ )
492
501
 
493
502
  return {
494
503
  spec: package_node_api.transfer_spec_gen4(created_package['contents_file_id'], Transfer::Spec::DIRECTION_SEND),
@@ -507,8 +516,10 @@ module Aspera
507
516
  transfer_spec.deep_merge!({
508
517
  'tags' => {
509
518
  Transfer::Spec::TAG_RESERVED => {
519
+ 'app' => app_info[:app],
510
520
  'usage_id' => "aspera.files.workspace.#{app_info[:workspace_id]}", # activity tracking
511
521
  'files' => {
522
+ 'node_id' => app_info[:node_info]['id'],
512
523
  'files_transfer_action' => "#{transfer_type}_#{app_info[:app].gsub(/s$/, '')}",
513
524
  'workspace_name' => app_info[:workspace_name], # activity tracking
514
525
  'workspace_id' => app_info[:workspace_id]
@@ -527,8 +538,15 @@ module Aspera
527
538
  case app_info[:app]
528
539
  when FILES_APP
529
540
  file_id = transfer_spec['tags'][Transfer::Spec::TAG_RESERVED]['node']['file_id']
530
- transfer_spec.deep_merge!({'tags' => {Transfer::Spec::TAG_RESERVED => {'files' => {'parentCwd' => "#{app_info[:node_info]['id']}:#{file_id}"}}}}) \
531
- unless transfer_spec.key?('remote_access_key')
541
+ transfer_spec.deep_merge!({
542
+ 'tags' => {
543
+ Transfer::Spec::TAG_RESERVED => {
544
+ 'files' => {
545
+ 'parentCwd' => "#{app_info[:node_info]['id']}:#{file_id}"
546
+ }
547
+ }
548
+ }
549
+ }) unless transfer_spec.key?('remote_access_key')
532
550
  when PACKAGES_APP
533
551
  transfer_spec.deep_merge!({
534
552
  'tags' => {
@@ -542,21 +560,14 @@ module Aspera
542
560
  }
543
561
  })
544
562
  end
545
- transfer_spec['tags'][Transfer::Spec::TAG_RESERVED]['files']['node_id'] = app_info[:node_info]['id']
546
- transfer_spec['tags'][Transfer::Spec::TAG_RESERVED]['app'] = app_info[:app]
547
563
  end
548
564
 
549
- ID_AK_ADMIN = 'ASPERA_ACCESS_KEY_ADMIN'
550
565
  # Callback from Plugins::Node
551
566
  # add application specific tags to permissions creation
552
567
  # @param perm_data [Hash] parameters for creating permissions
553
568
  # @param app_info [Hash] application information
554
569
  def permissions_set_create_params(perm_data:, app_info:)
555
- # workspace shared folder:
556
- # access_id = "#{ID_AK_ADMIN}_WS_#{app_info[:workspace_id]}"
557
570
  defaults = {
558
- # 'access_type' => 'user', # mandatory: user or group
559
- # 'access_id' => access_id, # id of user or group or special
560
571
  'tags' => {
561
572
  Transfer::Spec::TAG_RESERVED => {
562
573
  'files' => {
@@ -567,8 +578,6 @@ module Aspera
567
578
  'shared_by_user_id' => current_user_info['id'],
568
579
  'shared_by_name' => current_user_info['name'],
569
580
  'shared_by_email' => current_user_info['email'],
570
- # 'shared_with_name' => access_id,
571
- # 'share_as' => new_name_for_folder,
572
581
  'access_key' => app_info[:node_info]['access_key'],
573
582
  'node' => app_info[:node_info]['name']
574
583
  }
@@ -578,19 +587,20 @@ module Aspera
578
587
  }
579
588
  perm_data.deep_merge!(defaults)
580
589
  tag_workspace = perm_data['tags'][Transfer::Spec::TAG_RESERVED]['files']['workspace']
581
- case perm_data['with']
590
+ shared_with = perm_data.delete('with')
591
+ case shared_with
582
592
  when NilClass
583
593
  when ''
594
+ # workspace shared folder
584
595
  perm_data['access_type'] = 'user'
585
596
  perm_data['access_id'] = "#{ID_AK_ADMIN}_WS_#{app_info[:workspace_id]}"
586
597
  tag_workspace['shared_with_name'] = perm_data['access_id']
587
598
  else
588
- entity_info = lookup_by_name('contacts', perm_data['with'], query: {'current_workspace_id' => app_info[:workspace_id]})
599
+ entity_info = lookup_by_name('contacts', shared_with, query: {'current_workspace_id' => app_info[:workspace_id]})
589
600
  perm_data['access_type'] = entity_info['source_type']
590
601
  perm_data['access_id'] = entity_info['source_id']
591
- tag_workspace['shared_with_name'] = entity_info['email']
602
+ tag_workspace['shared_with_name'] = entity_info['email'] # TODO: check that ???
592
603
  end
593
- perm_data.delete('with')
594
604
  if perm_data.key?('as')
595
605
  tag_workspace['share_as'] = perm_data['as']
596
606
  perm_data.delete('as')
@@ -14,13 +14,13 @@ module Aspera
14
14
  def parameters_from_svc_credentials(service_credentials, bucket_region)
15
15
  # check necessary contents
16
16
  Aspera.assert_type(service_credentials, Hash){'service_credentials'}
17
- Aspera::Log.dump('service_credentials', service_credentials)
17
+ Log.dump(:service_credentials, service_credentials)
18
18
  %w[apikey resource_instance_id endpoints].each do |field|
19
19
  Aspera.assert(service_credentials.key?(field)){"service_credentials must have a field: #{field}"}
20
20
  end
21
21
  # read endpoints from service provided in service credentials
22
22
  endpoints = Aspera::Rest.new(base_url: service_credentials['endpoints']).read('')
23
- Aspera::Log.dump('endpoints', endpoints)
23
+ Log.dump(:endpoints, endpoints)
24
24
  endpoint = endpoints.dig('service-endpoints', 'regional', bucket_region, 'public', bucket_region)
25
25
  raise "no such region: #{bucket_region}" if endpoint.nil?
26
26
  return {
@@ -48,7 +48,8 @@ module Aspera
48
48
  grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
49
49
  response_type: 'cloud_iam',
50
50
  apikey: @api_key
51
- })
51
+ }
52
+ )
52
53
  # read FASP connection information for bucket
53
54
  xml_result_text = s3_api.call(
54
55
  operation: 'GET',
@@ -57,7 +58,7 @@ module Aspera
57
58
  query: {'faspConnectionInfo' => nil}
58
59
  )[:http].body
59
60
  ats_info = XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
60
- Aspera::Log.dump('ats_info', ats_info)
61
+ Log.dump(:ats_info, ats_info)
61
62
  @storage_credentials = {
62
63
  'type' => 'token',
63
64
  'token' => {TOKEN_FIELD => nil}
@@ -67,7 +68,8 @@ module Aspera
67
68
  auth: {
68
69
  type: :basic,
69
70
  username: ats_info['AccessKey']['Id'],
70
- password: ats_info['AccessKey']['Secret']},
71
+ password: ats_info['AccessKey']['Secret']
72
+ },
71
73
  add_tspec: {'tags'=>{Transfer::Spec::TAG_RESERVED=>{'node'=>{'storage_credentials'=>@storage_credentials}}}}
72
74
  )
73
75
  # update storage_credentials AND Rest params
@@ -67,9 +67,7 @@ module Aspera
67
67
  @shared_info[:read_exception].nil? &&
68
68
  (((@shared_info[:count][:sent_general] - @shared_info[:count][:received_general]) > 1) ||
69
69
  ((@shared_info[:count][:received_v2_delimiter] - @shared_info[:count][:sent_v2_delimiter]) > 1))
70
- if !@shared_info[:cond_var].wait(@shared_info[:mutex], 2.0)
71
- Log.log.trace1{"#{LOG_WS_SEND}#{'timeout'.blue}: #{@shared_info[:count]}"}
72
- end
70
+ Log.log.trace1{"#{LOG_WS_SEND}#{'timeout'.blue}: #{@shared_info[:count]}"} if !@shared_info[:cond_var].wait(@shared_info[:mutex], 2.0)
73
71
  end
74
72
  end
75
73
  end
@@ -110,7 +108,7 @@ module Aspera
110
108
  Log.log.debug{"#{LOG_WS_RECV}read thread started"}
111
109
  frame_parser = ::WebSocket::Frame::Incoming::Client.new(version: @ws_handshake.version)
112
110
  until @ws_io.eof?
113
- begin # rubocop:disable Style/RedundantBegin
111
+ begin
114
112
  # ready byte by byte until frame is ready
115
113
  # blocking read
116
114
  byte = @ws_io.read(1)
@@ -138,16 +136,16 @@ module Aspera
138
136
  def upload(transfer_spec)
139
137
  # identify this session uniquely
140
138
  session_id = SecureRandom.uuid
141
- @notify_cb&.call(:pre_start, session_id: nil, info: 'starting')
139
+ @notify_cb&.call(:sessions_init, info: 'starting')
142
140
  # process files to send, modify `paths` in transfer_spec
143
141
  files_to_send = process_upload_list(transfer_spec)
144
142
  # total size of all files is last element
145
143
  total_bytes_to_transfer = files_to_send.pop
146
- Log.log.trace1{Log.dump(:modified_tspec, transfer_spec)}
147
- Log.log.trace1{Log.dump(:files_to_send, files_to_send)}
144
+ Log.dump(:modified_tspec, transfer_spec, level: :trace1)
145
+ Log.dump(:files_to_send, files_to_send, level: :trace1)
148
146
  # TODO: check that this is available in endpoints: @api_info['endpoints']
149
147
  upload_url = File.join(@gw_root_url, @upload_version, 'upload')
150
- @notify_cb&.call(:pre_start, session_id: nil, info: 'connecting wss')
148
+ @notify_cb&.call(:sessions_init, info: 'connecting wss')
151
149
  # open web socket to end point (equivalent to Net::HTTP.start)
152
150
  http_session = Rest.start_http_session(upload_url)
153
151
  # get the underlying socket i/o
@@ -233,7 +231,8 @@ module Aspera
233
231
  end
234
232
  # throttling may have skipped last one
235
233
  @notify_cb&.call(:transfer, session_id: session_id, info: session_sent_bytes)
236
- @notify_cb&.call(:end, session_id: session_id)
234
+ @notify_cb&.call(:session_end, session_id: session_id)
235
+ @notify_cb&.call(:end)
237
236
  ws_send(ws_type: :close, data: nil)
238
237
  Log.log.debug("Finished upload, waiting for end of #{THR_RECV} thread.")
239
238
  @ws_read_thread.join
@@ -244,27 +243,29 @@ module Aspera
244
243
  end
245
244
 
246
245
  def download(transfer_spec)
247
- transfer_spec['zip_required'] ||= false
248
246
  transfer_spec['source_root'] ||= '/'
247
+ default_file_name = transfer_spec['paths'].first['source']
248
+ source_is_folder = %w[. /].include?(default_file_name)
249
+ default_file_name = 'http_download' if source_is_folder
250
+ transfer_spec['zip_required'] ||= source_is_folder || transfer_spec['paths'].length > 1
249
251
  # is normally provided by application, like package name
250
252
  if !transfer_spec.key?('download_name')
251
253
  # by default it is the name of first file
252
- download_name = File.basename(transfer_spec['paths'].first['source'], '.*')
253
- # ands add indication of number of files if there is more than one
254
- if transfer_spec['paths'].length > 1
255
- download_name += " #{transfer_spec['paths'].length} Files"
256
- end
254
+ download_name = File.basename(default_file_name, '.*')
255
+ # add indication of number of files if there is more than one
256
+ download_name += " #{transfer_spec['paths'].length} Files" if transfer_spec['paths'].length > 1
257
257
  transfer_spec['download_name'] = download_name
258
258
  end
259
+ # start transfer session on httpgw
259
260
  creation = create('download', {'transfer_spec' => transfer_spec})
260
261
  transfer_uuid = creation['url'].split('/').last
261
262
  file_name =
262
263
  if transfer_spec['zip_required'] || transfer_spec['paths'].length > 1
263
264
  # it is a zip file if zip is required or there is more than 1 file
264
- transfer_spec['download_name'] + '.zip'
265
+ "#{transfer_spec['download_name']}.zip"
265
266
  else
266
267
  # it is a plain file if we don't require zip and there is only one file
267
- File.basename(transfer_spec['paths'].first['source'])
268
+ File.basename(default_file_name)
268
269
  end
269
270
  file_path = File.join(transfer_spec['destination_root'], file_name)
270
271
  call(operation: 'GET', subpath: "download/#{transfer_uuid}", save_to_file: file_path)
@@ -288,13 +289,13 @@ module Aspera
288
289
  notify_cb: nil,
289
290
  **opts
290
291
  )
291
- Log.log.debug{Log.dump(:gw_url, url)}
292
+ Log.dump(:gw_url, url)
292
293
  # add scheme if missing
293
294
  url = "https://#{url}" unless url.match?(%r{^[a-z]{1,6}://})
294
- raise 'GW URL shall be with scheme https' unless url.start_with?('https://')
295
+ raise Error, 'GW URL shall be with scheme https' unless url.start_with?('https://')
295
296
  # remove trailing slash and version (o=only once) if present
296
297
  # TODO: issue warning ?
297
- url = url.gsub(%r{/+$}, '').gsub(%r{/#{API_V1}$}o, '')
298
+ url = url.chomp('/').gsub(%r{/#{API_V1}$}o, '')
298
299
  # assume GW is always under specific path (TODO: remove this ?)
299
300
  url = File.join(url, DEFAULT_BASE_PATH) unless url.end_with?(DEFAULT_BASE_PATH)
300
301
  @gw_root_url = url
@@ -305,7 +306,7 @@ module Aspera
305
306
  @notify_cb = notify_cb
306
307
  # get API info
307
308
  @api_info = read('info').freeze
308
- Log.log.debug{Log.dump(:api_info, @api_info)}
309
+ Log.dump(:api_info, @api_info)
309
310
  # web socket endpoint: by default use v2 (newer gateways), without base64 encoding
310
311
  # is the latest supported? else revert to old api
311
312
  if !@upload_version.eql?(API_V1)
@@ -328,7 +329,7 @@ module Aspera
328
329
  # @return [Array] info on files to send
329
330
  def process_upload_list(transfer_spec)
330
331
  total_bytes_to_transfer = 0
331
- source_prefix = transfer_spec.key?('source_root') && !transfer_spec['source_root'].empty? ? transfer_spec['source_root'] + '/' : ''
332
+ source_prefix = transfer_spec.key?('source_root') && !transfer_spec['source_root'].empty? ? "#{transfer_spec['source_root']}/" : ''
332
333
  files_to_send = []
333
334
  transfer_spec['paths'].each do |one_path|
334
335
  source_path = source_prefix + one_path['source']