aspera-cli 4.14.0 → 4.15.0

Sign up to get free protection for your applications and to get access to all the features.
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