aspera-cli 4.14.0 → 4.15.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 (90) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data/CHANGELOG.md +54 -3
  4. data/CONTRIBUTING.md +7 -7
  5. data/README.md +1457 -880
  6. data/bin/ascli +18 -9
  7. data/bin/asession +12 -14
  8. data/examples/proxy.pac +1 -1
  9. data/lib/aspera/aoc.rb +198 -127
  10. data/lib/aspera/ascmd.rb +24 -14
  11. data/lib/aspera/cli/basic_auth_plugin.rb +9 -6
  12. data/lib/aspera/cli/error.rb +17 -0
  13. data/lib/aspera/cli/extended_value.rb +47 -12
  14. data/lib/aspera/cli/formatter.rb +260 -171
  15. data/lib/aspera/cli/hints.rb +80 -0
  16. data/lib/aspera/cli/main.rb +101 -147
  17. data/lib/aspera/cli/manager.rb +160 -124
  18. data/lib/aspera/cli/plugin.rb +70 -59
  19. data/lib/aspera/cli/plugins/alee.rb +0 -1
  20. data/lib/aspera/cli/plugins/aoc.rb +239 -273
  21. data/lib/aspera/cli/plugins/ats.rb +8 -5
  22. data/lib/aspera/cli/plugins/bss.rb +2 -2
  23. data/lib/aspera/cli/plugins/config.rb +516 -375
  24. data/lib/aspera/cli/plugins/console.rb +40 -0
  25. data/lib/aspera/cli/plugins/cos.rb +4 -5
  26. data/lib/aspera/cli/plugins/faspex.rb +99 -84
  27. data/lib/aspera/cli/plugins/faspex5.rb +179 -148
  28. data/lib/aspera/cli/plugins/node.rb +219 -153
  29. data/lib/aspera/cli/plugins/orchestrator.rb +52 -17
  30. data/lib/aspera/cli/plugins/preview.rb +46 -32
  31. data/lib/aspera/cli/plugins/server.rb +57 -17
  32. data/lib/aspera/cli/plugins/shares.rb +34 -12
  33. data/lib/aspera/cli/sync_actions.rb +68 -0
  34. data/lib/aspera/cli/transfer_agent.rb +45 -55
  35. data/lib/aspera/cli/transfer_progress.rb +74 -0
  36. data/lib/aspera/cli/version.rb +1 -1
  37. data/lib/aspera/colors.rb +3 -1
  38. data/lib/aspera/command_line_builder.rb +14 -11
  39. data/lib/aspera/cos_node.rb +3 -2
  40. data/lib/aspera/environment.rb +17 -6
  41. data/lib/aspera/fasp/agent_aspera.rb +126 -0
  42. data/lib/aspera/fasp/agent_base.rb +31 -77
  43. data/lib/aspera/fasp/agent_connect.rb +21 -22
  44. data/lib/aspera/fasp/agent_direct.rb +88 -102
  45. data/lib/aspera/fasp/agent_httpgw.rb +196 -192
  46. data/lib/aspera/fasp/agent_node.rb +41 -34
  47. data/lib/aspera/fasp/agent_trsdk.rb +75 -34
  48. data/lib/aspera/fasp/error_info.rb +2 -2
  49. data/lib/aspera/fasp/faux_file.rb +52 -0
  50. data/lib/aspera/fasp/installation.rb +43 -184
  51. data/lib/aspera/fasp/management.rb +244 -0
  52. data/lib/aspera/fasp/parameters.rb +59 -26
  53. data/lib/aspera/fasp/parameters.yaml +75 -8
  54. data/lib/aspera/fasp/products.rb +162 -0
  55. data/lib/aspera/fasp/transfer_spec.rb +1 -1
  56. data/lib/aspera/fasp/uri.rb +4 -4
  57. data/lib/aspera/faspex_gw.rb +2 -2
  58. data/lib/aspera/faspex_postproc.rb +2 -2
  59. data/lib/aspera/hash_ext.rb +2 -2
  60. data/lib/aspera/json_rpc.rb +49 -0
  61. data/lib/aspera/line_logger.rb +23 -0
  62. data/lib/aspera/log.rb +57 -16
  63. data/lib/aspera/node.rb +97 -14
  64. data/lib/aspera/oauth.rb +36 -18
  65. data/lib/aspera/open_application.rb +4 -4
  66. data/lib/aspera/persistency_folder.rb +2 -2
  67. data/lib/aspera/preview/file_types.rb +4 -2
  68. data/lib/aspera/preview/generator.rb +22 -35
  69. data/lib/aspera/preview/options.rb +2 -0
  70. data/lib/aspera/preview/terminal.rb +24 -13
  71. data/lib/aspera/preview/utils.rb +19 -26
  72. data/lib/aspera/rest.rb +103 -72
  73. data/lib/aspera/rest_call_error.rb +1 -1
  74. data/lib/aspera/rest_error_analyzer.rb +15 -14
  75. data/lib/aspera/rest_errors_aspera.rb +37 -34
  76. data/lib/aspera/secret_hider.rb +14 -16
  77. data/lib/aspera/ssh.rb +4 -1
  78. data/lib/aspera/sync.rb +128 -122
  79. data/lib/aspera/temp_file_manager.rb +10 -3
  80. data/lib/aspera/web_auth.rb +10 -7
  81. data/lib/aspera/web_server_simple.rb +9 -4
  82. data.tar.gz.sig +0 -0
  83. metadata +33 -15
  84. metadata.gz.sig +0 -0
  85. data/lib/aspera/cli/listener/line_dump.rb +0 -19
  86. data/lib/aspera/cli/listener/logger.rb +0 -22
  87. data/lib/aspera/cli/listener/progress.rb +0 -50
  88. data/lib/aspera/cli/listener/progress_multi.rb +0 -84
  89. data/lib/aspera/cli/plugins/sync.rb +0 -44
  90. data/lib/aspera/fasp/listener.rb +0 -13
@@ -2,93 +2,47 @@
2
2
 
3
3
  module Aspera
4
4
  module Fasp
5
- # Base class for FASP transfer agents
6
- # sub classes shall implement start_transfer and shutdown
5
+ # Base class for transfer agents
7
6
  class AgentBase
8
- # fields description for JSON generation
9
- # spellchecker: disable
10
- INTEGER_FIELDS = %w[Bytescont FaspFileArgIndex StartByte Rate MinRate Port Priority RateCap MinRateCap TCPPort CreatePolicy TimePolicy
11
- DatagramSize XoptFlags VLinkVersion PeerVLinkVersion DSPipelineDepth PeerDSPipelineDepth ReadBlockSize WriteBlockSize
12
- ClusterNumNodes ClusterNodeId Size Written Loss FileBytes PreTransferBytes TransferBytes PMTU Elapsedusec ArgScansAttempted
13
- ArgScansCompleted PathScansAttempted FileScansCompleted TransfersAttempted TransfersPassed Delay].freeze
14
- BOOLEAN_FIELDS = %w[Encryption Remote RateLock MinRateLock PolicyLock FilesEncrypt FilesDecrypt VLinkLocalEnabled VLinkRemoteEnabled
15
- MoveRange Keepalive TestLogin UseProxy Precalc RTTAutocorrect].freeze
16
- EXPECTED_METHODS = %i[text struct enhanced].freeze
17
- private_constant :INTEGER_FIELDS, :BOOLEAN_FIELDS, :EXPECTED_METHODS
18
- # spellchecker: enable
19
-
20
7
  class << self
21
- # This checks the validity of the value returned by wait_for_transfers_completion
22
- # it must be a list of :success or exception
23
- def validate_status_list(statuses)
24
- raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
25
- raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) && !i.is_a?(StandardError)}.empty?
26
- end
27
- end
28
-
29
- private
30
-
31
- # translates legacy event into enhanced (JSON) event
32
- def enhanced_event_format(event)
33
- return event.keys.each_with_object({}) do |e, h|
34
- # capital_to_snake_case
35
- new_name = e
36
- .gsub(/([a-z\d])([A-Z])/, '\1_\2')
37
- .gsub(/([a-z\d])(usec)$/, '\1_\2')
38
- .gsub(/([A-Z]+)([A-Z][a-z])/, '\1_\2')
39
- .downcase
40
- value = event[e]
41
- value = value.to_i if INTEGER_FIELDS.include?(e)
42
- value = value.eql?('Yes') if BOOLEAN_FIELDS.include?(e)
43
- h[new_name] = value
44
- end
45
- end
46
-
47
- def initialize
48
- @listeners = []
49
- end
50
-
51
- def notify_listeners(current_event_text, current_event_data)
52
- Log.log.debug('send event to listeners')
53
- enhanced_event = nil
54
- @listeners.each do |listener|
55
- listener.event_text(current_event_text) if listener.respond_to?(:event_text)
56
- listener.event_struct(current_event_data) if listener.respond_to?(:event_struct)
57
- if listener.respond_to?(:event_enhanced)
58
- enhanced_event = enhanced_event_format(current_event_data) if enhanced_event.nil?
59
- listener.event_enhanced(enhanced_event)
8
+ # compute options from user provided and default options
9
+ def options(default:, options:)
10
+ result = options.symbolize_keys
11
+ available = default.map{|k, v|"#{k}(#{v})"}.join(', ')
12
+ result.each do |k, _v|
13
+ raise "Unknown transfer agent parameter: #{k}, expect one of #{available}" unless default.key?(k)
14
+ end
15
+ default.each do |k, v|
16
+ raise "Missing required agent parameter: #{k}. Parameters: #{available}" if v.eql?(:required) && !result.key?(k)
17
+ result[k] = v unless result.key?(k)
60
18
  end
19
+ return result
61
20
  end
62
- end # notify_listeners
63
-
64
- def notify_begin(id, size)
65
- notify_listeners('emulated', {LISTENER_SESSION_ID_B => id, 'Type' => 'NOTIFICATION', 'PreTransferBytes' => size})
66
21
  end
67
-
68
- def notify_progress(id, size)
69
- notify_listeners('emulated', {LISTENER_SESSION_ID_B => id, 'Type' => 'STATS', 'Bytescont' => size})
22
+ def wait_for_completion
23
+ # list of: :success or "error message string"
24
+ statuses = wait_for_transfers_completion
25
+ @progress&.reset
26
+ raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
27
+ raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) && !i.is_a?(StandardError)}.empty?
28
+ return statuses
70
29
  end
71
30
 
72
- def notify_end(id)
73
- notify_listeners('emulated', {LISTENER_SESSION_ID_B => id, 'Type' => 'DONE'})
74
- end
75
-
76
- public
77
-
78
- LISTENER_SESSION_ID_B = 'ListenerSessionId'
79
- LISTENER_SESSION_ID_S = 'listener_session_id'
31
+ private
80
32
 
81
- # listener receives events
82
- def add_listener(listener)
83
- raise "expect one of #{EXPECTED_METHODS}" if EXPECTED_METHODS.inject(0){|m, e|m + (listener.respond_to?("event_#{e}") ? 1 : 0)}.eql?(0)
84
- @listeners.push(listener)
85
- self
33
+ def initialize(options)
34
+ raise 'internal error' unless respond_to?(:start_transfer)
35
+ raise 'internal error' unless respond_to?(:wait_for_transfers_completion)
36
+ # method `shutdown` is optional
37
+ Log.log.debug{Log.dump(:agent_options, options)}
38
+ raise "transfer agent options expecting Hash, but have #{options.class}" unless options.is_a?(Hash)
39
+ @progress = options[:progress]
40
+ options.delete(:progress)
86
41
  end
87
42
 
88
- # the following methods must be implemented by subclass:
89
- # start_transfer(transfer_spec, token_regenerator: nil) : start transfer
90
- # wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
91
- # optional: shutdown
43
+ def notify_progress(**parameters)
44
+ @progress&.event(**parameters)
45
+ end
92
46
  end
93
47
  end
94
48
  end
@@ -4,7 +4,6 @@ require 'aspera/fasp/agent_base'
4
4
  require 'aspera/rest'
5
5
  require 'aspera/open_application'
6
6
  require 'securerandom'
7
- require 'tty-spinner'
8
7
 
9
8
  module Aspera
10
9
  module Fasp
@@ -14,20 +13,20 @@ module Aspera
14
13
  # delay between each try to start connect
15
14
  SLEEP_SEC_BETWEEN_RETRY = 3
16
15
  private_constant :CONNECT_START_URIS, :SLEEP_SEC_BETWEEN_RETRY
17
- def initialize(_options)
18
- super()
16
+ def initialize(options)
17
+ super(options)
19
18
  @connect_settings = {
20
19
  'app_id' => SecureRandom.uuid
21
20
  }
22
21
  raise 'Using connect requires a graphical environment' if !OpenApplication.default_gui_mode.eql?(:graphical)
23
22
  method_index = 0
24
23
  begin
25
- connect_url = Installation.instance.connect_uri
24
+ connect_url = Products.connect_uri
26
25
  Log.log.debug{"found: #{connect_url}"}
27
26
  @connect_api = Rest.new({base_url: "#{connect_url}/v5/connect", headers: {'Origin' => Rest.user_agent}}) # could use v6 also now
28
27
  connect_info = @connect_api.read('info/version')[:data]
29
28
  Log.log.info('Connect was reached') if method_index > 0
30
- Log.dump(:connect_version, connect_info)
29
+ Log.log.debug{Log.dump(:connect_version, connect_info)}
31
30
  rescue StandardError => e # Errno::ECONNREFUSED
32
31
  start_url = CONNECT_START_URIS[method_index]
33
32
  method_index += 1
@@ -74,10 +73,12 @@ module Aspera
74
73
  def wait_for_transfers_completion
75
74
  connect_activity_args = {'aspera_connect_settings' => @connect_settings}
76
75
  started = false
77
- spinner = nil
76
+ pre_calc = false
77
+ session_id = @xfer_id
78
78
  begin
79
79
  loop do
80
80
  tr_info = @connect_api.create("transfers/info/#{@xfer_id}", connect_activity_args)[:data]
81
+ Log.log.trace1{Log.dump(:tr_info, tr_info)}
81
82
  if tr_info['transfer_info'].is_a?(Hash)
82
83
  transfer = tr_info['transfer_info']
83
84
  if transfer.nil?
@@ -86,32 +87,30 @@ module Aspera
86
87
  end
87
88
  # TODO: get session id
88
89
  case transfer['status']
89
- when 'completed'
90
- notify_end(@connect_settings['app_id'])
91
- break
92
90
  when 'initiating', 'queued'
93
- if spinner.nil?
94
- spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
95
- spinner.start
96
- end
97
- spinner.update(title: transfer['status'])
98
- spinner.spin
91
+ notify_progress(session_id: nil, type: :pre_start, info: transfer['status'])
99
92
  when 'running'
100
- # puts "running: sessions:#{transfer['sessions'].length}, #{transfer['sessions'].map{|i| i['bytes_transferred']}.join(',')}"
101
- if !started && (transfer['bytes_expected'] != 0)
102
- spinner&.success
103
- notify_begin(@connect_settings['app_id'], transfer['bytes_expected'])
93
+ if !started
94
+ notify_progress(session_id: session_id, type: :session_start)
104
95
  started = true
96
+ end
97
+ if !pre_calc && (transfer['bytes_expected'] != 0)
98
+ notify_progress(type: :session_size, session_id: session_id, info: transfer['bytes_expected'])
99
+ pre_calc = true
105
100
  else
106
- notify_progress(@connect_settings['app_id'], transfer['bytes_written'])
101
+ notify_progress(type: :transfer, session_id: session_id, info: transfer['bytes_written'])
107
102
  end
103
+ when 'completed'
104
+ notify_progress(type: :end, session_id: session_id)
105
+ break
108
106
  when 'failed'
109
- spinner&.error
107
+ notify_progress(type: :end, session_id: session_id)
110
108
  raise Fasp::Error, transfer['error_desc']
111
109
  when 'cancelled'
112
- spinner&.error
110
+ notify_progress(type: :end, session_id: session_id)
113
111
  raise Fasp::Error, 'Transfer cancelled by user'
114
112
  else
113
+ notify_progress(type: :end, session_id: session_id)
115
114
  raise Fasp::Error, "unknown status: #{transfer['status']}: #{transfer['error_desc']}"
116
115
  end
117
116
  end
@@ -1,17 +1,18 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require 'English'
4
3
  require 'aspera/fasp/agent_base'
5
4
  require 'aspera/fasp/error'
6
5
  require 'aspera/fasp/parameters'
7
6
  require 'aspera/fasp/installation'
8
7
  require 'aspera/fasp/resume_policy'
9
8
  require 'aspera/fasp/transfer_spec'
9
+ require 'aspera/fasp/management'
10
10
  require 'aspera/log'
11
11
  require 'socket'
12
12
  require 'timeout'
13
13
  require 'securerandom'
14
14
  require 'shellwords'
15
+ require 'English'
15
16
 
16
17
  module Aspera
17
18
  module Fasp
@@ -25,8 +26,11 @@ module Aspera
25
26
  multi_incr_udp: true,
26
27
  resume: {},
27
28
  ascp_args: [],
28
- quiet: true # by default no native ascp progress bar
29
+ check_ignore: nil, # callback with host,port
30
+ quiet: true, # by default no native ascp progress bar
31
+ trusted_certs: [] # list of files with trusted certificates (stores)
29
32
  }.freeze
33
+ # spellchecker: enable
30
34
  private_constant :DEFAULT_OPTIONS
31
35
 
32
36
  # start ascp transfer (non blocking), single or multi-session
@@ -47,15 +51,7 @@ module Aspera
47
51
  # TODO: useful ? node only ?
48
52
  transfer_spec['tags'][Fasp::TransferSpec::TAG_RESERVED]['xfer_retry'] ||= 3600
49
53
  end
50
- Log.dump('ts', transfer_spec)
51
-
52
- # add bypass keys when authentication is token and no auth is provided
53
- if transfer_spec.key?('token') &&
54
- !transfer_spec.key?('remote_password') &&
55
- !transfer_spec.key?('EX_ssh_key_paths')
56
- # transfer_spec['remote_password'] = Installation.instance.bypass_pass # not used: no passphrase
57
- transfer_spec['EX_ssh_key_paths'] = Installation.instance.bypass_keys
58
- end
54
+ Log.log.debug{Log.dump('ts', transfer_spec)}
59
55
 
60
56
  # Compute this before using transfer spec because it potentially modifies the transfer spec
61
57
  # (even if the var is not used in single session)
@@ -81,16 +77,8 @@ module Aspera
81
77
  end
82
78
  end
83
79
 
84
- # compute known args
85
- env_args = Parameters.new(transfer_spec, @options).ascp_args
86
-
87
- # add fallback cert and key as arguments if needed
88
- if ['1', 1, true, 'force'].include?(transfer_spec['http_fallback'])
89
- env_args[:args].unshift('-Y', Installation.instance.path(:fallback_cert_privkey))
90
- env_args[:args].unshift('-I', Installation.instance.path(:fallback_certificate))
91
- end
92
-
93
- env_args[:args].unshift('-q') if @options[:quiet]
80
+ # compute known arguments and environment variables
81
+ env_args = Parameters.new(transfer_spec, @options).ascp_args
94
82
 
95
83
  # transfer job can be multi session
96
84
  xfer_job = {
@@ -162,6 +150,49 @@ module Aspera
162
150
  Log.log.debug('fasp local shutdown')
163
151
  end
164
152
 
153
+ # cspell:disable
154
+ # begin 'Type' => 'NOTIFICATION', 'PreTransferBytes' => size
155
+ # progress 'Type' => 'STATS', 'Bytescont' => size
156
+ # end 'Type' => 'DONE'
157
+ # cspell:enable
158
+
159
+ # @param event management port event
160
+ def process_progress(event)
161
+ session_id = event['SessionId']
162
+ case event['Type']
163
+ when 'INIT'
164
+ @pre_calc_sent = false
165
+ @pre_calc_last_size = nil
166
+ notify_progress(session_id: session_id, type: :session_start)
167
+ when 'NOTIFICATION' # sent from remote
168
+ if event.key?('PreTransferBytes')
169
+ @pre_calc_sent = true
170
+ notify_progress(session_id: session_id, type: :session_size, info: event['PreTransferBytes'])
171
+ end
172
+ when 'STATS' # during transfer
173
+ @pre_calc_last_size = event['TransferBytes'].to_i + event['StartByte'].to_i
174
+ notify_progress(session_id: session_id, type: :transfer, info: @pre_calc_last_size)
175
+ when 'DONE', 'ERROR' # end of session
176
+ total_size = event['TransferBytes'].to_i + event['StartByte'].to_i
177
+ if !@pre_calc_sent && !total_size.zero?
178
+ notify_progress(session_id: session_id, type: :session_size, info: total_size)
179
+ end
180
+ if @pre_calc_last_size != total_size
181
+ notify_progress(session_id: session_id, type: :transfer, info: total_size)
182
+ end
183
+ notify_progress(session_id: session_id, type: :end)
184
+ # cspell:disable
185
+ when 'SESSION'
186
+ when 'ARGSTOP'
187
+ when 'FILEERROR'
188
+ when 'STOP'
189
+ # cspell:enable
190
+ # stop event when one file is completed
191
+ else
192
+ Log.log.debug{"unknown event type #{event['Type']}"}
193
+ end
194
+ end
195
+
165
196
  # This is the low level method to start the "ascp" process
166
197
  # currently, relies on command line arguments
167
198
  # start ascp with management port.
@@ -173,8 +204,6 @@ module Aspera
173
204
  def start_transfer_with_args_env(env_args, session)
174
205
  raise 'env_args must be Hash' unless env_args.is_a?(Hash)
175
206
  raise 'session must be Hash' unless session.is_a?(Hash)
176
- # by default we assume an exception will be raised (for ensure block)
177
- exception_raised = true
178
207
  begin
179
208
  Log.log.debug{"env_args=#{env_args.inspect}"}
180
209
  # get location of ascp executable
@@ -183,13 +212,14 @@ module Aspera
183
212
  end
184
213
  # (optional) check it exists
185
214
  raise Fasp::Error, "no such file: #{ascp_path}" unless File.exist?(ascp_path)
215
+ notify_progress(session_id: nil, type: :pre_start, info: 'starting ascp')
186
216
  # open an available (0) local TCP port as ascp management
187
217
  mgt_sock = TCPServer.new('127.0.0.1', 0)
188
218
  # clone arguments as we eed to modify with mgt port
189
219
  ascp_arguments = env_args[:args].clone
190
220
  # add management port on the selected local port
191
221
  ascp_arguments.unshift('-M', mgt_sock.addr[1].to_s)
192
- # start ascp in sub process
222
+ # display ascp command line
193
223
  Log.log.debug do
194
224
  [
195
225
  'execute:',
@@ -198,12 +228,13 @@ module Aspera
198
228
  ascp_arguments.map{|a|Shellwords.shellescape(a)}
199
229
  ].flatten.join(' ')
200
230
  end
201
- # start process
231
+ # start ascp in separate process
202
232
  ascp_pid = Process.spawn(env_args[:env], [ascp_path, ascp_path], *ascp_arguments)
203
233
  # in parent, wait for connection to socket max 3 seconds
204
234
  Log.log.debug{"before accept for pid (#{ascp_pid})"}
205
235
  # init management socket
206
236
  ascp_mgt_io = nil
237
+ notify_progress(session_id: nil, type: :pre_start, info: 'waiting for ascp')
207
238
  Timeout.timeout(@options[:spawn_timeout_sec]) do
208
239
  ascp_mgt_io = mgt_sock.accept
209
240
  # management messages include file names which may be utf8
@@ -213,73 +244,35 @@ module Aspera
213
244
  end
214
245
  Log.log.debug{"after accept (#{ascp_mgt_io})"}
215
246
  session[:io] = ascp_mgt_io
216
- # exact text for event, with \n
217
- current_event_text = ''
218
- # parsed event (hash)
219
- current_event_data = nil
220
- # this is the last full status
221
- last_status_event = nil
222
- # read management port
223
- loop do
224
- # TODO: timeout here ?
225
- line = ascp_mgt_io.gets
226
- # nil when ascp process exits
227
- break if line.nil?
228
- current_event_text += line
229
- line.chomp!
230
- Log.log.debug{"line=[#{line}]"}
231
- case line
232
- when 'FASPMGR 2'
233
- # begin event
234
- current_event_data = {}
235
- current_event_text = ''
236
- when /^([^:]+): (.*)$/
237
- # event field
238
- current_event_data[Regexp.last_match(1)] = Regexp.last_match(2)
239
- when ''
240
- # empty line is separator to end event information
241
- raise 'unexpected empty line' if current_event_data.nil?
242
- current_event_data[AgentBase::LISTENER_SESSION_ID_B] = ascp_pid
243
- notify_listeners(current_event_text, current_event_data)
244
- case current_event_data['Type']
245
- when 'INIT'
246
- session[:id] = current_event_data['SessionId']
247
- Log.log.debug{"session id: #{session[:id]}"}
248
- when 'DONE', 'ERROR'
249
- # TODO: check if this is always the last event
250
- last_status_event = current_event_data
251
- end # event type
252
- else
253
- raise "unexpected line:[#{line}]"
254
- end # case
255
- end # loop (process mgt port lines)
247
+ processor = Management.new
248
+ # read management port, until socket is closed (gets returns nil)
249
+ while (line = ascp_mgt_io.gets)
250
+ event = processor.process_line(line.chomp)
251
+ next unless event
252
+ # event is ready
253
+ Log.log.debug{Log.dump(:management_port, event)}
254
+ # Log.log.trace1{"event: #{JSON.generate(Management.enhanced_event_format(event))}"}
255
+ process_progress(event)
256
+ Log.log.error((event['Description']).to_s) if event['Type'].eql?('FILEERROR') # cspell:disable-line
257
+ end
258
+ last_event = processor.last_event
256
259
  # check that last status was received before process exit
257
- if last_status_event.is_a?(Hash)
258
- case last_status_event['Type']
259
- when 'DONE'
260
- # all went well
261
- exception_raised = false
260
+ if last_event.is_a?(Hash)
261
+ case last_event['Type']
262
262
  when 'ERROR'
263
- Log.log.error{"code: #{last_status_event['Code']}"}
264
- if /bearer token/i.match?(last_status_event['Description'])
265
- Log.log.error('need to regenerate token'.red)
266
- if session[:token_regenerator].respond_to?(:refreshed_transfer_token)
267
- # regenerate token here, expired, or error on it
268
- # Note: in multi-session, each session will have a different one.
269
- env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
270
- end
263
+ if /bearer token/i.match?(last_event['Description']) &&
264
+ session[:token_regenerator].respond_to?(:refreshed_transfer_token)
265
+ # regenerate token here, expired, or error on it
266
+ # Note: in multi-session, each session will have a different one.
267
+ Log.log.warn('Regenerating bearer token')
268
+ env_args[:env]['ASPERA_SCP_TOKEN'] = session[:token_regenerator].refreshed_transfer_token
271
269
  end
272
- # cannot resolve address
273
- # if last_status_event['Code'].to_i.eql?(14)
274
- # Log.log.warn{"host: #{}"}
275
- # end
276
- raise Fasp::Error.new(last_status_event['Description'], last_status_event['Code'].to_i)
277
- else # case
278
- raise "unexpected last event type: #{last_status_event['Type']}"
279
- end
280
- else
281
- exception_raised = false
282
- Log.log.debug('no status read from ascp mgt port')
270
+ raise Fasp::Error.new(last_event['Description'], last_event['Code'].to_i)
271
+ when 'DONE'
272
+ nil
273
+ else
274
+ raise "unexpected last event type: #{last_event['Type']}"
275
+ end # case
283
276
  end
284
277
  rescue SystemCallError => e
285
278
  # Process.spawn
@@ -299,7 +292,7 @@ module Aspera
299
292
  if !status.success?
300
293
  message = "ascp failed with code #{status.exitstatus}"
301
294
  # raise error only if there was not already an exception
302
- raise Fasp::Error, message unless exception_raised
295
+ raise Fasp::Error, message unless $ERROR_INFO
303
296
  # else just debug, as main exception is already here
304
297
  Log.log.debug(message)
305
298
  end
@@ -323,7 +316,7 @@ module Aspera
323
316
  command = data
324
317
  .keys
325
318
  .map{|k|"#{k.capitalize}: #{data[k]}"}
326
- .unshift('FASPMGR 2')
319
+ .unshift(MGT_HEADER)
327
320
  .push('', '')
328
321
  .join("\n")
329
322
  session[:io].puts(command)
@@ -332,23 +325,16 @@ module Aspera
332
325
  private
333
326
 
334
327
  # @param options : keys(symbol): see DEFAULT_OPTIONS
335
- def initialize(options=nil)
336
- super()
328
+ def initialize(options={})
329
+ super(options)
337
330
  # all transfer jobs, key = SecureRandom.uuid, protected by mutex, cond var on change
338
331
  @jobs = {}
339
332
  # mutex protects global data accessed by threads
340
333
  @mutex = Mutex.new
341
334
  # set default options and override if specified
342
- @options = DEFAULT_OPTIONS.dup
343
- if !options.nil?
344
- raise "expecting Hash (or nil), but have #{options.class}" unless options.is_a?(Hash)
345
- options.each do |k, v|
346
- raise "Unknown local agent parameter: #{k}, expect one of #{DEFAULT_OPTIONS.keys.map(&:to_s).join(',')}" unless DEFAULT_OPTIONS.key?(k)
347
- @options[k] = v
348
- end
349
- end
350
- Log.log.debug{"local options= #{options}"}
335
+ @options = AgentBase.options(default: DEFAULT_OPTIONS, options: options)
351
336
  @resume_policy = ResumePolicy.new(@options[:resume].symbolize_keys)
337
+ Log.log.debug{Log.dump(:agent_options, @options)}
352
338
  end
353
339
 
354
340
  # transfer thread entry