aspera-cli 4.27.2 → 4.27.3

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 (130) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +50 -0
  4. data/bin/ascli +2 -1
  5. data/docs/README.md +804 -746
  6. data/lib/aspera/agent/connect.rb +6 -4
  7. data/lib/aspera/agent/desktop.rb +2 -2
  8. data/lib/aspera/agent/direct.rb +3 -1
  9. data/lib/aspera/agent/node.rb +3 -3
  10. data/lib/aspera/api/alee.rb +1 -1
  11. data/lib/aspera/api/aoc.rb +14 -12
  12. data/lib/aspera/api/ats.rb +1 -1
  13. data/lib/aspera/api/cos_node.rb +2 -2
  14. data/lib/aspera/api/faspex.rb +9 -7
  15. data/lib/aspera/api/httpgw.rb +37 -33
  16. data/lib/aspera/api/node.rb +38 -33
  17. data/lib/aspera/ascmd.rb +3 -1
  18. data/lib/aspera/ascp/installation.rb +62 -27
  19. data/lib/aspera/ascp/management.rb +1 -0
  20. data/lib/aspera/assert.rb +4 -0
  21. data/lib/aspera/cli/ascp_actions.rb +20 -41
  22. data/lib/aspera/cli/async_transfer_store.rb +2 -2
  23. data/lib/aspera/cli/bootstrapper.rb +11 -15
  24. data/lib/aspera/cli/command_line.rb +252 -0
  25. data/lib/aspera/cli/command_registry.rb +149 -33
  26. data/lib/aspera/cli/command_spec.rb +103 -14
  27. data/lib/aspera/cli/completion/ascli.bash +12 -0
  28. data/lib/aspera/cli/completion/ascli.fish +16 -0
  29. data/lib/aspera/cli/completion/ascli.zsh +19 -0
  30. data/lib/aspera/cli/context.rb +3 -0
  31. data/lib/aspera/cli/deprecation.rb +37 -0
  32. data/lib/aspera/cli/extended_value.rb +2 -0
  33. data/lib/aspera/cli/formatter.rb +87 -75
  34. data/lib/aspera/cli/gem_checker.rb +1 -1
  35. data/lib/aspera/cli/hints.rb +7 -6
  36. data/lib/aspera/cli/http.rb +21 -21
  37. data/lib/aspera/cli/info.rb +3 -0
  38. data/lib/aspera/cli/mcp_tool.rb +47 -83
  39. data/lib/aspera/cli/option_declarator.rb +33 -42
  40. data/lib/aspera/cli/option_registry.rb +69 -0
  41. data/lib/aspera/cli/option_types.rb +103 -0
  42. data/lib/aspera/cli/option_value.rb +281 -0
  43. data/lib/aspera/cli/options.schema.yaml +38 -5
  44. data/lib/aspera/cli/parser.rb +307 -848
  45. data/lib/aspera/cli/plugins/alee.rb +7 -4
  46. data/lib/aspera/cli/plugins/aoc.rb +430 -380
  47. data/lib/aspera/cli/plugins/ats.rb +58 -73
  48. data/lib/aspera/cli/plugins/base.rb +190 -240
  49. data/lib/aspera/cli/plugins/basic_auth.rb +2 -10
  50. data/lib/aspera/cli/plugins/config.rb +244 -178
  51. data/lib/aspera/cli/plugins/console.rb +102 -38
  52. data/lib/aspera/cli/plugins/cos.rb +6 -23
  53. data/lib/aspera/cli/plugins/factory.rb +3 -0
  54. data/lib/aspera/cli/plugins/faspex5.rb +176 -173
  55. data/lib/aspera/cli/plugins/faspio.rb +5 -10
  56. data/lib/aspera/cli/plugins/httpgw.rb +8 -11
  57. data/lib/aspera/cli/plugins/mcp.rb +20 -55
  58. data/lib/aspera/cli/plugins/node.rb +277 -311
  59. data/lib/aspera/cli/plugins/orchestrator.rb +90 -77
  60. data/lib/aspera/cli/plugins/preview.rb +79 -90
  61. data/lib/aspera/cli/plugins/server.rb +76 -50
  62. data/lib/aspera/cli/plugins/shares.rb +68 -116
  63. data/lib/aspera/cli/preset_actions.rb +17 -10
  64. data/lib/aspera/cli/preset_manager.rb +12 -2
  65. data/lib/aspera/cli/prompt.rb +35 -0
  66. data/lib/aspera/cli/result.rb +13 -18
  67. data/lib/aspera/cli/runner.rb +31 -54
  68. data/lib/aspera/cli/special_values.rb +5 -0
  69. data/lib/aspera/cli/sync_actions.rb +41 -37
  70. data/lib/aspera/cli/terminal_formatter.rb +9 -3
  71. data/lib/aspera/cli/transfer_actions.rb +0 -6
  72. data/lib/aspera/cli/transfer_agent.rb +29 -35
  73. data/lib/aspera/cli/vault_manager.rb +0 -17
  74. data/lib/aspera/cli/version.rb +1 -1
  75. data/lib/aspera/cli/wizard.rb +4 -2
  76. data/lib/aspera/coverage.rb +1 -0
  77. data/lib/aspera/environment.rb +6 -0
  78. data/lib/aspera/faspex_gw.rb +2 -1
  79. data/lib/aspera/faspex_postproc.rb +1 -0
  80. data/lib/aspera/graphql.rb +5 -5
  81. data/lib/aspera/json_rpc/client.rb +5 -5
  82. data/lib/aspera/keychain/encrypted_hash.rb +1 -1
  83. data/lib/aspera/keychain/one_password_api.rb +1 -1
  84. data/lib/aspera/link_header.rb +2 -2
  85. data/lib/aspera/log.rb +22 -25
  86. data/lib/aspera/markdown.rb +2 -0
  87. data/lib/aspera/mime.rb +25 -0
  88. data/lib/aspera/node_simulator.rb +1 -0
  89. data/lib/aspera/oauth/base.rb +35 -25
  90. data/lib/aspera/oauth/factory.rb +1 -0
  91. data/lib/aspera/oauth/generic.rb +1 -1
  92. data/lib/aspera/oauth/jwt.rb +1 -1
  93. data/lib/aspera/oauth/web.rb +9 -8
  94. data/lib/aspera/preview/file_types.rb +4 -4
  95. data/lib/aspera/preview/generator.rb +7 -0
  96. data/lib/aspera/preview/options.rb +4 -4
  97. data/lib/aspera/preview/terminal.rb +4 -3
  98. data/lib/aspera/preview/utils.rb +9 -6
  99. data/lib/aspera/products/connect.rb +1 -1
  100. data/lib/aspera/rainbow.rb +7 -0
  101. data/lib/aspera/rest/aspera_errors.rb +60 -0
  102. data/lib/aspera/rest/call_error.rb +27 -0
  103. data/lib/aspera/rest/client.rb +514 -0
  104. data/lib/aspera/rest/error_analyzer.rb +113 -0
  105. data/lib/aspera/rest/list.rb +143 -0
  106. data/lib/aspera/rest/parameters.rb +55 -0
  107. data/lib/aspera/rest/util.rb +176 -0
  108. data/lib/aspera/rest.rb +7 -621
  109. data/lib/aspera/schema/IBM Aspera Console-enhanced.yaml +1125 -0
  110. data/lib/aspera/schema/IBM Aspera on Cloud Automation API-1.0.5-enhanced.yaml +2395 -0
  111. data/lib/aspera/schema/documentation.rb +13 -3
  112. data/lib/aspera/schema/registry.rb +18 -1
  113. data/lib/aspera/schema/validator.rb +92 -0
  114. data/lib/aspera/secret_hider.rb +36 -25
  115. data/lib/aspera/string_ext.rb +15 -0
  116. data/lib/aspera/temp_file_manager.rb +6 -5
  117. data/lib/aspera/transfer/parameters.rb +2 -0
  118. data/lib/aspera/transfer/spec.rb +1 -0
  119. data/lib/aspera/uri_reader.rb +11 -11
  120. data/lib/aspera/web_auth/index.html +147 -0
  121. data/lib/aspera/web_auth/server.rb +81 -0
  122. data.tar.gz.sig +0 -0
  123. metadata +39 -7
  124. metadata.gz.sig +0 -0
  125. data/lib/aspera/colors.rb +0 -79
  126. data/lib/aspera/rest_call_error.rb +0 -25
  127. data/lib/aspera/rest_error_analyzer.rb +0 -111
  128. data/lib/aspera/rest_errors_aspera.rb +0 -58
  129. data/lib/aspera/rest_list.rb +0 -136
  130. data/lib/aspera/web_auth.rb +0 -211
@@ -6,6 +6,8 @@ require 'aspera/products/other'
6
6
  require 'aspera/rest'
7
7
  require 'aspera/environment'
8
8
  require 'securerandom'
9
+ require 'aspera/rainbow'
10
+ using Rainbow
9
11
 
10
12
  module Aspera
11
13
  module Agent
@@ -21,9 +23,9 @@ module Aspera
21
23
  # agent_params must contain 'app_id'; the Connect URL is auto-discovered.
22
24
  # @return [Hash] normalized status hash
23
25
  def transfer_status(transfer_id, agent_params)
24
- connect_api = Rest.new(
26
+ connect_api = Rest::Client.new(
25
27
  base_url: "#{connect_api_url}/v5/connect",
26
- headers: {'Origin' => RestParameters.instance.user_agent}
28
+ headers: {'Origin' => Rest::Parameters.instance.user_agent}
27
29
  )
28
30
  tr_info = connect_api.create("transfers/info/#{transfer_id}", {'aspera_connect_settings' => {'app_id' => agent_params['app_id']}})
29
31
  transfer = tr_info['transfer_info']
@@ -72,9 +74,9 @@ module Aspera
72
74
  # raise exception if connect not started and file does not exist
73
75
  connect_url = self.class.connect_api_url
74
76
  Log.log.debug { "found: #{connect_url}" }
75
- @connect_api = Rest.new(
77
+ @connect_api = Rest::Client.new(
76
78
  base_url: "#{connect_url}/v5/connect", # could use v6 also now
77
- headers: {'Origin' => RestParameters.instance.user_agent}
79
+ headers: {'Origin' => Rest::Parameters.instance.user_agent}
78
80
  )
79
81
  connect_info = @connect_api.read('info/version')
80
82
  Log.log.debug('Connect was reached') if method_index > 0
@@ -24,7 +24,7 @@ module Aspera
24
24
  # @return [Hash] normalized status hash
25
25
  def transfer_status(transfer_id, agent_params)
26
26
  app_id = agent_params['application_id']
27
- client = Aspera::JsonRpc::Client.new(Aspera::Rest.new(base_url: desktop_api_url))
27
+ client = Aspera::JsonRpc::Client.new(Aspera::Rest::Client.new(base_url: desktop_api_url))
28
28
  raw = client.get_transfer(app_id: app_id, transfer_id: transfer_id)
29
29
  normalize_status(raw['status'], bytes: raw['bytes_written'].to_i, error: raw['error_desc'])
30
30
  end
@@ -70,7 +70,7 @@ module Aspera
70
70
  begin
71
71
  # curl 'http://127.0.0.1:33024/' -X POST -H 'content-type: application/json' --data-raw '{"jsonrpc":"2.0","params":[],"id":999999,"method":"rpc.discover"}'
72
72
  # https://playground.open-rpc.org/?schemaUrl=http://127.0.0.1:33024
73
- @client_app_api = Aspera::JsonRpc::Client.new(Aspera::Rest.new(base_url: aspera_client_api_url))
73
+ @client_app_api = Aspera::JsonRpc::Client.new(Aspera::Rest::Client.new(base_url: aspera_client_api_url))
74
74
  client_info = @client_app_api.get_info
75
75
  Log.dump(:client_version, client_info)
76
76
  Log.log.debug('Client was reached') if method_index > 0
@@ -14,6 +14,8 @@ require 'socket'
14
14
  require 'securerandom'
15
15
  require 'shellwords'
16
16
  require 'English'
17
+ require 'aspera/rainbow'
18
+ using Rainbow
17
19
 
18
20
  module Aspera
19
21
  module Agent
@@ -258,7 +260,7 @@ module Aspera
258
260
  @resume_policy.execute_with_resume do
259
261
  start_and_monitor_process(session: session, exec_spec: session[:exec_spec])
260
262
  end
261
- Log.log.debug('transfer ok'.bg_green)
263
+ Log.log.debug('transfer ok'.bg(:green))
262
264
  rescue => e
263
265
  session[:error] = e
264
266
  raise if Log.log.debug? || !e.is_a?(Transfer::Error)
@@ -24,7 +24,7 @@ module Aspera
24
24
  else
25
25
  rest_params[:auth] = {type: :basic, username: agent_params['username'], password: agent_params['password']}
26
26
  end
27
- node_api = Rest.new(**rest_params)
27
+ node_api = Rest::Client.new(**rest_params)
28
28
  data = node_api.read("ops/transfers/#{transfer_id}") || {'status' => 'unknown'}
29
29
  normalize_status(data)
30
30
  end
@@ -71,7 +71,7 @@ module Aspera
71
71
  password: password
72
72
  }
73
73
  end
74
- @node_api = Rest.new(**rest_params)
74
+ @node_api = Rest::Client.new(**rest_params)
75
75
  # TODO: currently only supports one transfer. This is bad shortcut. but ok for CLI.
76
76
  @transfer_id = nil
77
77
  end
@@ -149,7 +149,7 @@ module Aspera
149
149
 
150
150
  # used internally to ensure node api is set before using.
151
151
  def node_api_
152
- Aspera.assert(!@node_api.nil?, 'Before using this object, set the node_api attribute to a Aspera::Rest object')
152
+ Aspera.assert(!@node_api.nil?, 'Before using this object, set the node_api attribute to a Aspera::Rest::Client object')
153
153
  return @node_api
154
154
  end
155
155
  end
@@ -4,7 +4,7 @@ require 'aspera/api/aoc'
4
4
  module Aspera
5
5
  module Api
6
6
  # Aspera License Entitlement Engine API client
7
- class Alee < Aspera::Rest
7
+ class Alee < Aspera::Rest::Client
8
8
  def initialize(entitlement_id, customer_id, api_domain: AoC::SAAS_DOMAIN_PROD, version: 'v1')
9
9
  super(
10
10
  base_url: "https://api.#{api_domain}/metering/#{version}",
@@ -7,16 +7,18 @@ require 'aspera/hash_ext'
7
7
  require 'aspera/data_repository'
8
8
  require 'aspera/transfer/spec'
9
9
  require 'aspera/api/node'
10
- require 'aspera/rest_list'
10
+ require 'aspera/rest/list'
11
11
  require 'base64'
12
+ require 'aspera/rainbow'
13
+ using Rainbow
12
14
 
13
15
  module Aspera
14
16
  module Api
15
17
  # information relative to Files or Packages App
16
18
 
17
19
  # Aspera on Cloud API client
18
- class AoC < Rest
19
- include RestList
20
+ class AoC < Rest::Client
21
+ include Rest::List
20
22
 
21
23
  module AppType
22
24
  FILES = 'files'
@@ -140,7 +142,7 @@ module Aspera
140
142
  # @param url [String] URL of AoC public link
141
143
  # @return [Hash{Symbol => String, Hash}] Information about public link, or nil if not a public link
142
144
  def link_info(url)
143
- final_uri = Rest.new(base_url: url, redirect_max: MAX_AOC_URL_REDIRECT).call(operation: 'GET', ret: :resp).uri
145
+ final_uri = Rest::Client.new(base_url: url, redirect_max: MAX_AOC_URL_REDIRECT).call(operation: 'GET', ret: :resp).uri
144
146
  Log.dump(:final_uri, final_uri, level: :trace1)
145
147
  org_domain = split_org_domain(final_uri)
146
148
  if (m = final_uri.path.match(%r{/oauth2/([^/]+)/login$}))
@@ -202,8 +204,8 @@ module Aspera
202
204
  Aspera.assert(block_given?, 'block required for call_paging')
203
205
  # set default large page if user does not specify own parameters. AoC Caps to 1000 anyway
204
206
  query['per_page'] = 1000 unless query.key?('per_page')
205
- max_items = query.delete(RestList::MAX_ITEMS)
206
- max_pages = query.delete(RestList::MAX_PAGES)
207
+ max_items = query.delete(Rest::List::MAX_ITEMS)
208
+ max_pages = query.delete(Rest::List::MAX_PAGES)
207
209
  item_list = []
208
210
  total_count = nil
209
211
  current_page = query['page']
@@ -225,9 +227,9 @@ module Aspera
225
227
  break if !max_items.nil? && item_list.count >= max_items
226
228
  break if !max_pages.nil? && page_count >= max_pages
227
229
  break if total_count&.<=(item_list.count)
228
- RestParameters.instance.spinner_cb.call("#{item_list.count} / #{total_count}") unless total_count.eql?(item_list.count.to_s)
230
+ Rest::Parameters.instance.spinner_cb.call("#{item_list.count} / #{total_count}") unless total_count.eql?(item_list.count.to_s)
229
231
  end
230
- RestParameters.instance.spinner_cb.call(action: :success)
232
+ Rest::Parameters.instance.spinner_cb.call(action: :success)
231
233
  item_list = item_list[0..max_items - 1] if !max_items.nil? && item_list.count > max_items
232
234
  return {items: item_list, total: total_count}
233
235
  end
@@ -394,7 +396,7 @@ module Aspera
394
396
  @cache_user_info =
395
397
  begin
396
398
  read('self')
397
- rescue Aspera::RestCallError => e
399
+ rescue Aspera::Rest::CallError => e
398
400
  raise if exception || e.message.include?('invalid_grant')
399
401
  Log.log.debug { "Ignoring error: (#{e.class}) #{e}" }
400
402
  {}
@@ -496,9 +498,9 @@ module Aspera
496
498
  }
497
499
  else
498
500
  # OAuth bearer token
499
- node_params[:auth] = auth_params.clone
500
- node_params[:auth][:params] ||= {}
501
- node_params[:auth][:params][:scope] = Node.token_scope(node_info['access_key'], scope)
501
+ # copy nested params, so that auth params of this API are not modified
502
+ node_params[:auth] = auth_params.merge(params: auth_params[:params]&.dup || {})
503
+ node_params[:auth][:params][:scope] = Node::Scope.join(access_key: node_info['access_key'], scope: scope)
502
504
  node_params[:auth][:params][:owner_access] = true if scope.eql?(Node::Scope::ADMIN)
503
505
  # Special header required for bearer token only
504
506
  node_params[:headers] = {Node::HEADER_X_ASPERA_ACCESS_KEY => node_info['access_key']}
@@ -6,7 +6,7 @@ require 'aspera/rest'
6
6
  module Aspera
7
7
  module Api
8
8
  # Aspera Transfer Service API client without authentication
9
- class Ats < Aspera::Rest
9
+ class Ats < Aspera::Rest::Client
10
10
  SERVICE_BASE_URL = 'https://ats.aspera.io'
11
11
  # currently supported clouds
12
12
  # Note to Aspera: shall be an API call
@@ -22,7 +22,7 @@ module Aspera
22
22
  Aspera.assert(service_credentials.key?(field)) { "service_credentials must have a field: #{field}" }
23
23
  end
24
24
  # read endpoints from service provided in service credentials
25
- endpoints = Aspera::Rest.new(base_url: service_credentials['endpoints']).read('')
25
+ endpoints = Aspera::Rest::Client.new(base_url: service_credentials['endpoints']).read('')
26
26
  Log.dump(:endpoints, endpoints)
27
27
  endpoint = endpoints.dig('service-endpoints', 'regional', bucket_region, 'public', bucket_region)
28
28
  Aspera.assert(!endpoint.nil?) { "no such region: #{bucket_region}" }
@@ -40,7 +40,7 @@ module Aspera
40
40
  endpoint = "https://#{endpoint}" unless endpoint.start_with?('http')
41
41
  @auth_url = auth_url
42
42
  @api_key = api_key
43
- s3_api = Aspera::Rest.new(
43
+ s3_api = Aspera::Rest::Client.new(
44
44
  base_url: endpoint,
45
45
  not_auth_codes: %w[401 403], # error codes when not authorized
46
46
  headers: {'ibm-service-instance-id' => instance_id},
@@ -3,7 +3,7 @@
3
3
  require 'aspera/assert'
4
4
  require 'aspera/rest'
5
5
  require 'aspera/oauth/base'
6
- require 'aspera/rest_list'
6
+ require 'aspera/rest/list'
7
7
  require 'digest'
8
8
 
9
9
  module Aspera
@@ -15,6 +15,7 @@ module Aspera
15
15
  # @param context [String] The `context` query parameter in public link
16
16
  # @param redirect_uri [String] URI of web UI login
17
17
  # @param path_authorize [String] Path to provide passcode
18
+ # @param base_params [Hash] Parameters for base class
18
19
  def initialize(
19
20
  context:,
20
21
  redirect_uri:,
@@ -48,18 +49,18 @@ module Aspera
48
49
  raise Error, info['action_message'] if info['action_message']
49
50
  Aspera.assert(info['code'], 'Missing code in answer')
50
51
  # Exchange code for token
51
- return create_token_call(base_params.merge(
52
+ create_token_base(
52
53
  grant_type: 'authorization_code',
53
54
  code: info['code'],
54
55
  redirect_uri: @redirect_uri
55
- ))
56
+ )
56
57
  end
57
58
  end
58
59
  OAuth::Factory.instance.register_token_creator(FaspexPubLink)
59
60
  module Api
60
61
  # Aspera Faspex 5 API Client
61
- class Faspex < Aspera::Rest
62
- include RestList
62
+ class Faspex < Aspera::Rest::Client
63
+ include Rest::List
63
64
 
64
65
  # endpoint for authentication API
65
66
  PATH_AUTH = 'auth'
@@ -141,6 +142,7 @@ module Aspera
141
142
 
142
143
  # @param url [String] Faspex URL, can be a public link
143
144
  # @param auth [Symbol] Authentication method: :boot (token in header), :web (open browser), :jwt (client_id + private key), :public_link (context in URL)
145
+ # @param root [String] Root path for API
144
146
  # @param password [String, nil] For :boot auth, the token copied directly from browser in developer mode
145
147
  # @param client_id [String, nil] For :web and :jwt auth, the client_id of web UI application
146
148
  # @param client_secret [String, nil] For :web auth, the client_secret of web UI application (not needed for :jwt)
@@ -166,7 +168,7 @@ module Aspera
166
168
  case auth
167
169
  when :public_link
168
170
  # Get URL of final redirect of provided public link
169
- final_url = Rest.new(base_url: url, redirect_max: 3).call(operation: 'GET', ret: :resp).uri.to_s
171
+ final_url = Rest::Client.new(base_url: url, redirect_max: 3).call(operation: 'GET', ret: :resp).uri.to_s
170
172
  Log.dump(:final_url, final_url, level: :trace1)
171
173
  # Get context from query
172
174
  encoded_context = Rest.query_to_h(URI.parse(final_url).query)['context']
@@ -178,7 +180,7 @@ module Aspera
178
180
  base_url = final_url.gsub(%r{/public/.*}, '').gsub(/\?.*/, '')
179
181
  # Get web UI client_id and redirect_uri
180
182
  # TODO: change this for something more reliable
181
- config = JSON.parse(Rest.new(base_url: "#{base_url}/config.js", redirect_max: 3).call(operation: 'GET').sub(/^[^=]+=/, '').gsub(/([a-z_]+):/, '"\1":').delete("\n ").tr("'", '"')).symbolize_keys
183
+ config = JSON.parse(Rest::Client.new(base_url: "#{base_url}/config.js", redirect_max: 3).call(operation: 'GET').sub(/^[^=]+=/, '').gsub(/([a-z_]+):/, '"\1":').delete("\n ").tr("'", '"')).symbolize_keys
182
184
  Log.dump(:config_js, config)
183
185
  {
184
186
  base_url: "#{base_url}/#{root}",
@@ -9,8 +9,10 @@ require 'securerandom'
9
9
  require 'websocket'
10
10
  require 'base64'
11
11
  require 'json'
12
+ require 'aspera/rainbow'
13
+ using Rainbow
12
14
 
13
- # throw exception on error, instead of error code
15
+ # Throw exception on error, instead of error code
14
16
  WebSocket.should_raise = true
15
17
 
16
18
  module Aspera
@@ -29,7 +31,7 @@ module Aspera
29
31
  # 1 JSON.slice_upload File start "end_slice_upload" sent_v2_delimiter
30
32
  # 2.. Binary File binary chunks "end upload" sent_general
31
33
  # last JSON.slice_upload File end "end_slice_upload" sent_v2_delimiter
32
- class Httpgw < Aspera::Rest
34
+ class Httpgw < Aspera::Rest::Client
33
35
  DEFAULT_BASE_PATH = '/aspera/http-gwy'
34
36
  INFO_ENDPOINT = 'info'
35
37
  MSG_SEND_TRANSFER_SPEC = 'transfer_spec'
@@ -40,27 +42,24 @@ module Aspera
40
42
  API_V1 = 'v1'
41
43
  API_V2 = 'v2'
42
44
  THR_RECV = 'recv'
43
- LOG_WS_SEND = 'ws: send: '.red
44
- LOG_WS_RECV = "ws: #{THR_RECV}: ".green
45
45
  private_constant :MSG_RECV_DATA_RECEIVED_SIGNAL, :MSG_RECV_SLICE_UPLOAD_SIGNAL
46
- # send message on http gw web socket
46
+
47
+ # Send message on http gw web socket
48
+ # @param msg_type [String] type of message to send
49
+ # @param payload [Hash] Data to send
47
50
  def ws_snd_json(msg_type, payload)
48
51
  if msg_type.eql?(MSG_SEND_SLICE_UPLOAD) && @upload_version.eql?(API_V2)
49
52
  @shared_info[:count][:sent_v2_delimiter] += 1
50
53
  else
51
54
  @shared_info[:count][:sent_general] += 1
52
55
  end
53
- Log.log.trace1 do
54
- log_data = payload.dup
55
- log_data[:data] = "[data #{log_data[:data].length} bytes]" if log_data.key?(:data)
56
- "#{LOG_WS_SEND}json: #{msg_type}: #{JSON.generate(log_data)}"
57
- end
56
+ Log.dump("#{log_ws_send} #{msg_type}", level: :trace1) { payload.dup.tap { |d| d[:data] = "[data #{d[:data].length} bytes]" if d.key?(:data) } }
58
57
  ws_send(ws_type: :text, data: JSON.generate({msg_type => payload}))
59
58
  end
60
59
 
61
60
  # send data on http gw web socket
62
61
  def ws_send(ws_type:, data:)
63
- Log.log.trace1 { "#{LOG_WS_SEND}sending: #{ws_type} (#{data&.length || 0} bytes)" }
62
+ Log.log.trace1 { "#{log_ws_send}sending: #{ws_type} (#{data&.length || 0} bytes)" }
64
63
  @shared_info[:count][:sent_general] += 1 if ws_type.eql?(:binary)
65
64
  frame_generator = ::WebSocket::Frame::Outgoing::Client.new(data: data, type: ws_type, version: @ws_handshake.version)
66
65
  @ws_io.write(frame_generator.to_s)
@@ -72,12 +71,12 @@ module Aspera
72
71
  @shared_info[:read_exception].nil? &&
73
72
  (((@shared_info[:count][:sent_general] - @shared_info[:count][:received_general]) > 1) ||
74
73
  ((@shared_info[:count][:received_v2_delimiter] - @shared_info[:count][:sent_v2_delimiter]) > 1))
75
- Log.log.trace1 { "#{LOG_WS_SEND}#{'timeout'.blue}: #{@shared_info[:count]}" } if !@shared_info[:cond_var].wait(@shared_info[:mutex], 2.0)
74
+ Log.log.trace1 { "#{log_ws_send}#{'timeout'.blue}: #{@shared_info[:count]}" } if !@shared_info[:cond_var].wait(@shared_info[:mutex], 2.0)
76
75
  end
77
76
  end
78
77
  end
79
78
  raise @shared_info[:read_exception] unless @shared_info[:read_exception].nil?
80
- Log.log.trace2 { "#{LOG_WS_SEND}counts: #{@shared_info[:count]}" }
79
+ Log.log.trace2 { "#{log_ws_send}counts: #{@shared_info[:count]}" }
81
80
  end
82
81
 
83
82
  # Check header ourself and give precise error message, as websocket will only throw error without details
@@ -85,13 +84,13 @@ module Aspera
85
84
  # @return [String] Response Header
86
85
  def validated_ws_response_header(header)
87
86
  first_line = header.split("\r\n").first
88
- raise RestCallError.new({messages: ["Unexpected: #{first_line}", 'Expected: 101 Switching Protocols']}) unless first_line.split(/\s+/, 3)[1].eql?('101')
87
+ raise Rest::CallError.new({messages: ["Unexpected: #{first_line}", 'Expected: 101 Switching Protocols']}) unless first_line.split(/\s+/, 3)[1].eql?('101')
89
88
  header
90
89
  end
91
90
 
92
91
  # message processing for read thread
93
92
  def process_received_message(message)
94
- Log.log.debug { "#{LOG_WS_RECV}message: [#{message}] (#{message.class})" }
93
+ Log.log.debug { "#{log_ws_recv}message: [#{message}] (#{message.class})" }
95
94
  if message.eql?(MSG_RECV_DATA_RECEIVED_SIGNAL)
96
95
  @shared_info[:mutex].synchronize do
97
96
  @shared_info[:count][:received_general] += 1
@@ -117,23 +116,23 @@ module Aspera
117
116
  end
118
117
  end
119
118
 
120
- # main function of read thread
119
+ # Main function of read thread
121
120
  def process_read_thread
122
- Log.log.debug { "#{LOG_WS_RECV}read thread started" }
121
+ Log.log.debug { "#{log_ws_recv}read thread started" }
123
122
  frame_parser = ::WebSocket::Frame::Incoming::Client.new(version: @ws_handshake.version)
124
123
  until @ws_io.eof?
125
124
  begin
126
125
  # ready byte by byte until frame is ready
127
126
  # blocking read
128
127
  byte = @ws_io.read(1)
129
- Log.log.trace2 { "#{LOG_WS_RECV}read: #{byte} (#{byte.class}) eof=#{@ws_io.eof?}" }
128
+ Log.log.trace2 { "#{log_ws_recv}read: #{byte} (#{byte.class}) eof=#{@ws_io.eof?}" }
130
129
  frame_parser << byte
131
130
  frame_ok = frame_parser.next
132
131
  next if frame_ok.nil?
133
132
  process_received_message(frame_ok.data.to_s)
134
- Log.log.trace2 { "#{LOG_WS_RECV}counts: #{@shared_info[:count]}" }
133
+ Log.log.trace2 { "#{log_ws_recv}counts: #{@shared_info[:count]}" }
135
134
  rescue => e
136
- Log.log.debug { "#{LOG_WS_RECV}Exception: #{e}" }
135
+ Log.log.debug { "#{log_ws_recv}Exception: #{e}" }
137
136
  @shared_info[:mutex].synchronize do
138
137
  @shared_info[:read_exception] = e
139
138
  @shared_info[:cond_var].signal
@@ -142,9 +141,9 @@ module Aspera
142
141
  end
143
142
  end
144
143
  Log.log.debug do
145
- "#{LOG_WS_RECV}exception: #{@shared_info[:read_exception]},cls=#{@shared_info[:read_exception].class})"
144
+ "#{log_ws_recv}exception: #{@shared_info[:read_exception]},cls=#{@shared_info[:read_exception].class})"
146
145
  end unless @shared_info[:read_exception].nil?
147
- Log.log.debug { "#{LOG_WS_RECV}read thread stopped (ws eof=#{@ws_io.eof?})" }
146
+ Log.log.debug { "#{log_ws_recv}read thread stopped (ws eof=#{@ws_io.eof?})" }
148
147
  end
149
148
 
150
149
  def upload(transfer_spec)
@@ -170,7 +169,7 @@ module Aspera
170
169
  # Get whole HTTP response header, Check and process
171
170
  # no need to check `finished?` or result of `<<` (true), as we give the whole header at once
172
171
  @ws_handshake << validated_ws_response_header(@ws_io.readuntil("\r\n\r\n"))
173
- Log.log.debug { "#{LOG_WS_SEND}handshake success" }
172
+ Log.log.debug { "#{log_ws_send}handshake success" }
174
173
  # data shared between main thread and read thread
175
174
  @shared_info = {
176
175
  read_exception: nil, # error message if any in callback
@@ -224,7 +223,7 @@ module Aspera
224
223
  # send once, before data, at beginning
225
224
  ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?(0)
226
225
  ws_send(ws_type: :binary, data: slice_bin_data)
227
- Log.log.trace1 { "#{LOG_WS_SEND}buffer: file: #{file_index}, slice: #{slice_info[:slice]}/#{last_slice}" }
226
+ Log.log.trace1 { "#{log_ws_send}buffer: file: #{file_index}, slice: #{slice_info[:slice]}/#{last_slice}" }
228
227
  # send once, after data, at end
229
228
  ws_snd_json(MSG_SEND_SLICE_UPLOAD, slice_info) if slice_info[:slice].eql?(last_slice)
230
229
  end
@@ -283,17 +282,20 @@ module Aspera
283
282
  # it is a plain file if we don't require zip and there is only one file
284
283
  File.basename(default_file_name)
285
284
  end
286
- file_path = File.join(transfer_spec['destination_root'], file_name)
287
- call(operation: 'GET', subpath: "download/#{transfer_uuid}", save_to: file_path)
285
+ call(
286
+ operation: 'GET',
287
+ subpath: "download/#{transfer_uuid}",
288
+ save_to: File.join(transfer_spec['destination_root'], file_name)
289
+ )
288
290
  end
289
291
 
290
292
  def info
291
- return @api_info
293
+ @api_info
292
294
  end
293
295
 
294
296
  # @return [String] the base url of the gateway
295
297
  def base_url
296
- return @gw_root_url
298
+ @gw_root_url
297
299
  end
298
300
 
299
301
  # @param url [String] URL of the HTTP Gateway, without version
@@ -306,10 +308,10 @@ module Aspera
306
308
  **opts
307
309
  )
308
310
  Log.dump(:gw_url, url)
309
- # add scheme if missing
311
+ # Add scheme if missing
310
312
  url = "https://#{url}" unless url.match?(%r{^[a-z]{1,6}://})
311
313
  Aspera.assert(url.start_with?('https://'), type: Error) { 'GW URL shall be with scheme https' }
312
- # remove trailing slash and version (o=only once) if present
314
+ # Remove trailing slash and version (o=only once) if present
313
315
  # TODO: issue warning ?
314
316
  url = url.chomp('/').gsub(%r{/#{API_V1}$}o, '')
315
317
  # assume GW is always under specific path (TODO: remove this ?)
@@ -339,6 +341,10 @@ module Aspera
339
341
 
340
342
  private
341
343
 
344
+ # Log prefixes (evaluated on each call, as colors can be enabled or disabled by option)
345
+ def log_ws_send = 'ws: send: '.red
346
+ def log_ws_recv = "ws: #{THR_RECV}: ".green
347
+
342
348
  # compute total size of files to upload (for progress)
343
349
  # modify transfer spec to be suitable for HTTPGW
344
350
  # @param transfer_spec [Hash] transfer specification
@@ -372,8 +378,7 @@ module Aspera
372
378
  until folders_to_process.empty?
373
379
  folder = folders_to_process.shift
374
380
  # read all entries
375
- Dir.entries(folder).each do |entry|
376
- next if entry.eql?('.') || entry.eql?('..')
381
+ Dir.each_child(folder) do |entry|
377
382
  entry_path = File.join(folder, entry)
378
383
  if File.directory?(entry_path)
379
384
  folders_to_process.push(entry_path)
@@ -396,7 +401,6 @@ module Aspera
396
401
  end
397
402
  transfer_spec['paths'] = files_to_send.map { |i| {'source' => i[:name]} }
398
403
  files_to_send.push(total_bytes_to_transfer)
399
- return files_to_send
400
404
  end
401
405
  end
402
406
  end