aspera-cli 4.1.0 → 4.3.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 (42) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +455 -229
  3. data/docs/Makefile +4 -4
  4. data/docs/README.erb.md +457 -126
  5. data/docs/test_env.conf +19 -2
  6. data/examples/aoc.rb +14 -3
  7. data/examples/faspex4.rb +89 -0
  8. data/lib/aspera/aoc.rb +38 -40
  9. data/lib/aspera/cli/main.rb +65 -33
  10. data/lib/aspera/cli/plugins/aoc.rb +54 -65
  11. data/lib/aspera/cli/plugins/ats.rb +2 -2
  12. data/lib/aspera/cli/plugins/config.rb +158 -137
  13. data/lib/aspera/cli/plugins/faspex.rb +111 -64
  14. data/lib/aspera/cli/plugins/faspex5.rb +35 -48
  15. data/lib/aspera/cli/plugins/node.rb +3 -2
  16. data/lib/aspera/cli/plugins/preview.rb +88 -55
  17. data/lib/aspera/cli/transfer_agent.rb +98 -62
  18. data/lib/aspera/cli/version.rb +1 -1
  19. data/lib/aspera/command_line_builder.rb +48 -31
  20. data/lib/aspera/cos_node.rb +34 -28
  21. data/lib/aspera/environment.rb +2 -2
  22. data/lib/aspera/fasp/aoc.rb +1 -1
  23. data/lib/aspera/fasp/installation.rb +68 -45
  24. data/lib/aspera/fasp/local.rb +89 -45
  25. data/lib/aspera/fasp/manager.rb +3 -0
  26. data/lib/aspera/fasp/node.rb +23 -1
  27. data/lib/aspera/fasp/parameters.rb +57 -86
  28. data/lib/aspera/fasp/parameters.yaml +531 -0
  29. data/lib/aspera/fasp/resume_policy.rb +13 -12
  30. data/lib/aspera/fasp/uri.rb +1 -1
  31. data/lib/aspera/id_generator.rb +22 -0
  32. data/lib/aspera/node.rb +14 -3
  33. data/lib/aspera/oauth.rb +135 -129
  34. data/lib/aspera/persistency_action_once.rb +11 -7
  35. data/lib/aspera/persistency_folder.rb +6 -26
  36. data/lib/aspera/rest.rb +3 -12
  37. data/lib/aspera/secrets.rb +20 -0
  38. data/lib/aspera/sync.rb +40 -35
  39. data/lib/aspera/timer_limiter.rb +22 -0
  40. data/lib/aspera/web_auth.rb +105 -0
  41. metadata +22 -3
  42. data/docs/transfer_spec.html +0 -99
@@ -4,7 +4,10 @@ require 'aspera/preview/options'
4
4
  require 'aspera/preview/utils'
5
5
  require 'aspera/preview/file_types'
6
6
  require 'aspera/persistency_action_once'
7
+ require 'aspera/node'
7
8
  require 'aspera/hash_ext'
9
+ require 'aspera/timer_limiter'
10
+ require 'aspera/id_generator'
8
11
  require 'date'
9
12
  require 'securerandom'
10
13
 
@@ -23,7 +26,8 @@ module Aspera
23
26
  DEFAULT_PREVIEWS_FOLDER='previews'
24
27
  AK_MARKER_FILE='.aspera_access_key'
25
28
  LOCAL_STORAGE_PCVL='file:///'
26
- private_constant :PREV_GEN_TAG, :PREVIEW_FOLDER_SUFFIX, :PREVIEW_BASENAME, :TMP_DIR_PREFIX, :DEFAULT_PREVIEWS_FOLDER, :LOCAL_STORAGE_PCVL, :AK_MARKER_FILE
29
+ LOG_LIMITER_SEC=30.0
30
+ private_constant :PREV_GEN_TAG, :PREVIEW_FOLDER_SUFFIX, :PREVIEW_BASENAME, :TMP_DIR_PREFIX, :DEFAULT_PREVIEWS_FOLDER, :LOCAL_STORAGE_PCVL, :AK_MARKER_FILE, :LOG_LIMITER_SEC
27
31
 
28
32
  # option_skip_format has special accessors
29
33
  attr_accessor :option_previews_folder
@@ -39,6 +43,8 @@ module Aspera
39
43
  @preview_formats_to_generate=Aspera::Preview::Generator::PREVIEW_FORMATS.clone
40
44
  # options for generation
41
45
  @gen_options=Aspera::Preview::Options.new
46
+ # used to trigger periodic processing
47
+ @periodic=TimerLimiter.new(LOG_LIMITER_SEC)
42
48
  # link CLI options to gen_info attributes
43
49
  self.options.set_obj_attr(:skip_format,self,:option_skip_format,[]) # no skip
44
50
  self.options.set_obj_attr(:folder_reset_cache,self,:option_folder_reset_cache,:no)
@@ -112,13 +118,14 @@ module Aspera
112
118
  end
113
119
 
114
120
  # old version based on folders
115
- def process_trevents(iteration_token)
121
+ # @param iteration_persistency can be nil
122
+ def process_trevents(iteration_persistency)
116
123
  events_filter={
117
124
  'access_key'=>@access_key_self['id'],
118
125
  'type'=>'download.ended'
119
126
  }
120
- # optionally by iteration token
121
- events_filter['iteration_token']=iteration_token unless iteration_token.nil?
127
+ # optionally add iteration token from persistency
128
+ events_filter['iteration_token']=iteration_persistency.data.first unless iteration_persistency.nil?
122
129
  begin
123
130
  events=@api_node.read('events',events_filter)[:data]
124
131
  rescue RestCallError => e
@@ -131,47 +138,63 @@ module Aspera
131
138
  end
132
139
  return if events.empty?
133
140
  events.each do |event|
134
- next unless event['data']['direction'].eql?('receive')
135
- next unless event['data']['status'].eql?('completed')
136
- next unless event['data']['error_code'].eql?(0)
137
- next unless event['data'].dig('tags','aspera',PREV_GEN_TAG).nil?
138
- folder_id=event.dig('data','tags','aspera','node','file_id')
139
- folder_id||=event.dig('data','file_id')
140
- next if folder_id.nil?
141
- folder_entry=@api_node.read("files/#{folder_id}")[:data] rescue nil
142
- next if folder_entry.nil?
143
- scan_folder_files(folder_entry)
141
+ if event['data']['direction'].eql?('receive') and
142
+ event['data']['status'].eql?('completed') and
143
+ event['data']['error_code'].eql?(0) and
144
+ event['data'].dig('tags','aspera',PREV_GEN_TAG).nil?
145
+ folder_id=event.dig('data','tags','aspera','node','file_id')
146
+ folder_id||=event.dig('data','file_id')
147
+ if !folder_id.nil?
148
+ folder_entry=@api_node.read("files/#{folder_id}")[:data] rescue nil
149
+ scan_folder_files(folder_entry) unless folder_entry.nil?
150
+ end
151
+ end
152
+ if @periodic.trigger? or event.equal?(events.last)
153
+ Log.log.info("Processed event #{event['id']}")
154
+ # save checkpoint to avoid losing processing in case of error
155
+ if !iteration_persistency.nil?
156
+ iteration_persistency.data[0]=event['id'].to_s
157
+ iteration_persistency.save
158
+ end
159
+ end
144
160
  end
145
- return events.last['id'].to_s
146
161
  end
147
162
 
148
163
  # requests recent events on node api and process newly modified folders
149
- def process_events(iteration_token)
164
+ def process_events(iteration_persistency)
150
165
  # get new file creation by access key (TODO: what if file already existed?)
151
166
  events_filter={
152
167
  'access_key'=>@access_key_self['id'],
153
168
  'type'=>'file.*'
154
169
  }
155
- # and optionally by iteration token
156
- events_filter['iteration_token']=iteration_token unless iteration_token.nil?
170
+ # optionally add iteration token from persistency
171
+ events_filter['iteration_token']=iteration_persistency.data.first unless iteration_persistency.nil?
157
172
  events=@api_node.read('events',events_filter)[:data]
158
173
  return if events.empty?
159
174
  events.each do |event|
160
175
  # process only files
161
- next unless event.dig('data','type').eql?('file')
162
- file_entry=@api_node.read("files/#{event['data']['id']}")[:data] rescue nil
163
- next if file_entry.nil?
164
- next unless @option_skip_folders.select{|d|file_entry['path'].start_with?(d)}.empty?
165
- file_entry['parent_file_id']=event['data']['parent_file_id']
166
- if event['types'].include?('file.deleted')
167
- Log.log.error('TODO'.red)
176
+ if event.dig('data','type').eql?('file')
177
+ file_entry=@api_node.read("files/#{event['data']['id']}")[:data] rescue nil
178
+ if !file_entry.nil? and
179
+ @option_skip_folders.select{|d|file_entry['path'].start_with?(d)}.empty?
180
+ file_entry['parent_file_id']=event['data']['parent_file_id']
181
+ if event['types'].include?('file.deleted')
182
+ Log.log.error('TODO'.red)
183
+ end
184
+ if event['types'].include?('file.deleted')
185
+ generate_preview(file_entry)
186
+ end
187
+ end
168
188
  end
169
- if event['types'].include?('file.deleted')
170
- generate_preview(file_entry)
189
+ if @periodic.trigger? or event.equal?(events.last)
190
+ Log.log.info("Processing event #{event['id']}")
191
+ # save checkpoint to avoid losing processing in case of error
192
+ if !iteration_persistency.nil?
193
+ iteration_persistency.data[0]=event['id'].to_s
194
+ iteration_persistency.save
195
+ end
171
196
  end
172
197
  end
173
- # write new iteration file
174
- return events.last['id'].to_s
175
198
  end
176
199
 
177
200
  def do_transfer(direction,folder_id,source_filename,destination='/')
@@ -322,12 +345,14 @@ module Aspera
322
345
  end # generate_preview
323
346
 
324
347
  # scan all files in provided folder entry
348
+ # @param scan_start subpath to start folder scan inside
325
349
  def scan_folder_files(top_entry,scan_start=nil)
326
350
  if !scan_start.nil?
327
351
  # canonical path: start with / and ends with /
328
352
  scan_start='/'+scan_start.split('/').select{|i|!i.empty?}.join('/')
329
353
  scan_start="#{scan_start}/" #unless scan_start.end_with?('/')
330
354
  end
355
+ filter_block=Aspera::Node.file_matcher(options.get_option(:value,:optional))
331
356
  Log.log.debug("scan: #{top_entry} : #{scan_start}".green)
332
357
  # don't use recursive call, use list instead
333
358
  entries_to_process=[top_entry]
@@ -335,36 +360,45 @@ module Aspera
335
360
  entry=entries_to_process.shift
336
361
  # process this entry only if it is within the scan_start
337
362
  entry_path_with_slash=entry['path']
363
+ Log.log.info("processing entry #{entry_path_with_slash}") if @periodic.trigger?
338
364
  entry_path_with_slash="#{entry_path_with_slash}/" unless entry_path_with_slash.end_with?('/')
339
365
  if !scan_start.nil? and !scan_start.start_with?(entry_path_with_slash) and !entry_path_with_slash.start_with?(scan_start)
340
366
  Log.log.debug("#{entry['path']} folder (skip start)".bg_red)
341
367
  next
342
368
  end
343
369
  Log.log.debug("item:#{entry}")
344
- case entry['type']
345
- when 'file'
346
- generate_preview(entry)
347
- when 'link'
348
- Log.log.debug('Ignoring link.')
349
- when 'folder'
350
- if @option_skip_folders.include?(entry['path'])
351
- Log.log.debug("#{entry['path']} folder (skip list)".bg_red)
352
- else
353
- Log.log.debug("#{entry['path']} folder".green)
354
- # get folder content
355
- folder_entries=get_folder_entries(entry['id'])
356
- # process all items in current folder
357
- folder_entries.each do |folder_entry|
358
- # add path for older versions of ES
359
- if !folder_entry.has_key?('path')
360
- folder_entry['path']=entry_path_with_slash+folder_entry['name']
370
+ begin
371
+ case entry['type']
372
+ when 'file'
373
+ if filter_block.call(entry)
374
+ generate_preview(entry)
375
+ else
376
+ Log.log.debug('skip by filter')
377
+ end
378
+ when 'link'
379
+ Log.log.debug('Ignoring link.')
380
+ when 'folder'
381
+ if @option_skip_folders.include?(entry['path'])
382
+ Log.log.debug("#{entry['path']} folder (skip list)".bg_red)
383
+ else
384
+ Log.log.debug("#{entry['path']} folder".green)
385
+ # get folder content
386
+ folder_entries=get_folder_entries(entry['id'])
387
+ # process all items in current folder
388
+ folder_entries.each do |folder_entry|
389
+ # add path for older versions of ES
390
+ if !folder_entry.has_key?('path')
391
+ folder_entry['path']=entry_path_with_slash+folder_entry['name']
392
+ end
393
+ folder_entry['parent_file_id']=entry['id']
394
+ entries_to_process.push(folder_entry)
361
395
  end
362
- folder_entry['parent_file_id']=entry['id']
363
- entries_to_process.push(folder_entry)
364
396
  end
397
+ else
398
+ Log.log.warn("unknown entry type: #{entry['type']}")
365
399
  end
366
- else
367
- Log.log.warn("unknown entry type: #{entry['type']}")
400
+ rescue => e
401
+ Log.log.warn("An error occured: #{e}, ignoring")
368
402
  end
369
403
  end
370
404
  end
@@ -430,16 +464,15 @@ module Aspera
430
464
  scan_folder_files(folder_info,scan_path)
431
465
  return Main.result_status('scan finished')
432
466
  when :events,:trevents
433
- iteration_data=[]
434
467
  iteration_persistency=nil
435
468
  if self.options.get_option(:once_only,:mandatory)
436
469
  iteration_persistency=PersistencyActionOnce.new(
437
470
  manager: @agents[:persistency],
438
- data: iteration_data,
439
- ids: ["preview_iteration_#{command}",self.options.get_option(:url,:mandatory),self.options.get_option(:username,:mandatory)])
471
+ data: [],
472
+ id: IdGenerator.from_list(['preview_iteration',command.to_s,self.options.get_option(:url,:mandatory),self.options.get_option(:username,:mandatory)]))
440
473
  end
441
- iteration_data[0]=send("process_#{command}",iteration_data[0])
442
- iteration_persistency.save unless iteration_persistency.nil?
474
+ # call processing method specified by command line command
475
+ send("process_#{command}",iteration_persistency)
443
476
  return Main.result_status("#{command} finished")
444
477
  when :check
445
478
  Aspera::Preview::Utils.check_tools(@skip_types)
@@ -10,41 +10,56 @@ module Aspera
10
10
  module Cli
11
11
  # The Transfer agent is a common interface to start a transfer using
12
12
  # one of the supported transfer agents
13
- # provides CLI options to select one of the transfer agents (fasp client)
13
+ # provides CLI options to select one of the transfer agents (FASP/ascp client)
14
14
  class TransferAgent
15
15
  # special value for --sources : read file list from arguments
16
16
  FILE_LIST_FROM_ARGS='@args'
17
17
  # special value for --sources : read file list from transfer spec (--ts)
18
18
  FILE_LIST_FROM_TRANSFER_SPEC='@ts'
19
- private_constant :FILE_LIST_FROM_ARGS,:FILE_LIST_FROM_TRANSFER_SPEC
20
- # @param cli_objects external objects: option manager, config file manager
21
- def initialize(cli_objects)
22
- @opt_mgr=cli_objects[:options]
23
- @config=cli_objects[:config]
24
- # transfer spec overrides provided on command line
25
- @transfer_spec_cmdline={"create_dir"=>true}
19
+ DEFAULT_TRANSFER_NOTIF_TMPL=<<END_OF_TEMPLATE
20
+ From: <%=from_name%> <<%=from_email%>>
21
+ To: <<%=to%>>
22
+ Subject: <%=subject%>
23
+
24
+ Transfer is: <%=global_transfer_status%>
25
+
26
+ <%=ts.to_yaml%>
27
+ END_OF_TEMPLATE
28
+ #%
29
+ private_constant :FILE_LIST_FROM_ARGS,:FILE_LIST_FROM_TRANSFER_SPEC,:DEFAULT_TRANSFER_NOTIF_TMPL
30
+ # @param env external objects: option manager, config file manager
31
+ def initialize(env)
32
+ # same as plugin environment
33
+ @env=env
34
+ # command line can override transfer spec
35
+ @transfer_spec_cmdline={'create_dir'=>true}
26
36
  # the currently selected transfer agent
27
37
  @agent=nil
28
38
  @progress_listener=Listener::ProgressMulti.new
29
39
  # source/destination pair, like "paths" of transfer spec
30
40
  @transfer_paths=nil
31
- @opt_mgr.set_obj_attr(:ts,self,:option_transfer_spec)
32
- @opt_mgr.add_opt_simple(:ts,"override transfer spec values (Hash, use @json: prefix), current=#{@opt_mgr.get_option(:ts,:optional)}")
33
- @opt_mgr.add_opt_simple(:local_resume,"set resume policy (Hash, use @json: prefix), current=#{@opt_mgr.get_option(:local_resume,:optional)}")
34
- @opt_mgr.add_opt_simple(:to_folder,"destination folder for downloaded files")
35
- @opt_mgr.add_opt_simple(:sources,"list of source files (see doc)")
36
- @opt_mgr.add_opt_simple(:transfer_info,"additional information for transfer client")
37
- @opt_mgr.add_opt_list(:src_type,[:list,:pair],"type of file list")
38
- @opt_mgr.add_opt_list(:transfer,[:direct,:httpgw,:connect,:node,:aoc],"type of transfer")
39
- @opt_mgr.add_opt_list(:progress,[:none,:native,:multi],"type of progress bar")
40
- @opt_mgr.set_option(:transfer,:direct)
41
- @opt_mgr.set_option(:src_type,:list)
42
- @opt_mgr.set_option(:progress,:native) # use native ascp progress bar as it is more reliable
43
- @opt_mgr.parse_options!
41
+ options.set_obj_attr(:ts,self,:option_transfer_spec)
42
+ options.add_opt_simple(:ts,"override transfer spec values (Hash, use @json: prefix), current=#{options.get_option(:ts,:optional)}")
43
+ options.add_opt_simple(:local_resume,"set resume policy (Hash, use @json: prefix), current=#{options.get_option(:local_resume,:optional)}")
44
+ options.add_opt_simple(:to_folder,"destination folder for downloaded files")
45
+ options.add_opt_simple(:sources,"list of source files (see doc)")
46
+ options.add_opt_simple(:transfer_info,"additional information for transfer client")
47
+ options.add_opt_list(:src_type,[:list,:pair],"type of file list")
48
+ options.add_opt_list(:transfer,[:direct,:httpgw,:connect,:node,:aoc],"type of transfer")
49
+ options.add_opt_list(:progress,[:none,:native,:multi],"type of progress bar")
50
+ options.set_option(:transfer,:direct)
51
+ options.set_option(:src_type,:list)
52
+ options.set_option(:progress,:native) # use native ascp progress bar as it is more reliable
53
+ options.parse_options!
44
54
  end
45
55
 
56
+ def options; @env[:options];end
57
+
58
+ def config; @env[:config];end
59
+
46
60
  def option_transfer_spec; @transfer_spec_cmdline; end
47
61
 
62
+ # multiple option are merged
48
63
  def option_transfer_spec=(value); @transfer_spec_cmdline.merge!(value); end
49
64
 
50
65
  def option_transfer_spec_deep_merge(ts); @transfer_spec_cmdline.deep_merge!(ts); end
@@ -53,8 +68,8 @@ module Aspera
53
68
  @agent=instance
54
69
  @agent.add_listener(Listener::Logger.new)
55
70
  # use local progress bar if asked so, or if native and non local ascp (because only local ascp has native progress bar)
56
- if @opt_mgr.get_option(:progress,:mandatory).eql?(:multi) or
57
- (@opt_mgr.get_option(:progress,:mandatory).eql?(:native) and !@opt_mgr.get_option(:transfer,:mandatory).eql?(:direct))
71
+ if options.get_option(:progress,:mandatory).eql?(:multi) or
72
+ (options.get_option(:progress,:mandatory).eql?(:native) and !options.get_option(:transfer,:mandatory).eql?(:direct))
58
73
  @agent.add_listener(@progress_listener)
59
74
  end
60
75
  end
@@ -62,51 +77,59 @@ module Aspera
62
77
  # analyze options and create new agent if not already created or set
63
78
  def set_agent_by_options
64
79
  return nil unless @agent.nil?
65
- agent_type=@opt_mgr.get_option(:transfer,:mandatory)
80
+ agent_type=options.get_option(:transfer,:mandatory)
66
81
  case agent_type
67
82
  when :direct
68
- agent_options=@opt_mgr.get_option(:transfer_info,:optional)
83
+ agent_options=options.get_option(:transfer_info,:optional)
69
84
  agent_options=agent_options.symbolize_keys if agent_options.is_a?(Hash)
70
85
  new_agent=Fasp::Local.new(agent_options)
71
- new_agent.quiet=false if @opt_mgr.get_option(:progress,:mandatory).eql?(:native)
86
+ new_agent.quiet=false if options.get_option(:progress,:mandatory).eql?(:native)
72
87
  when :httpgw
73
- httpgw_config=@opt_mgr.get_option(:transfer_info,:mandatory)
88
+ httpgw_config=options.get_option(:transfer_info,:mandatory)
74
89
  new_agent=Fasp::HttpGW.new(httpgw_config)
75
90
  when :connect
76
91
  new_agent=Fasp::Connect.new
77
92
  when :node
78
- # way for code to setup alternate node api in avance
93
+ # way for code to setup alternate node api in advance
79
94
  # support: @preset:<name>
80
95
  # support extended values
81
- node_config=@opt_mgr.get_option(:transfer_info,:optional)
96
+ node_config=options.get_option(:transfer_info,:optional)
82
97
  # if not specified: use default node
83
98
  if node_config.nil?
84
- param_set_name=@config.get_plugin_default_config_name(:node)
99
+ param_set_name=config.get_plugin_default_config_name(:node)
85
100
  raise CliBadArgument,"No default node configured, Please specify --#{:transfer_info.to_s.gsub('_','-')}" if param_set_name.nil?
86
- node_config=@config.preset_by_name(param_set_name)
101
+ node_config=config.preset_by_name(param_set_name)
87
102
  end
88
103
  Log.log.debug("node=#{node_config}")
89
- raise CliBadArgument,"the node configuration shall be Hash, not #{node_config.class} (#{node_config}), use either @json:<json> or @preset:<parameter set name>" if !node_config.is_a?(Hash)
90
- # now check there are required parameters
91
- sym_config=[:url,:username,:password].inject({}) do |h,param|
92
- raise CliBadArgument,"missing parameter [#{param}] in node specification: #{node_config}" if !node_config.has_key?(param.to_s)
93
- h[param]=node_config[param.to_s]
94
- h
104
+ raise CliBadArgument,"the node configuration shall be Hash, not #{node_config.class} (#{node_config}), use either @json:<json> or @preset:<parameter set name>" unless node_config.is_a?(Hash)
105
+ # here, node_config is a Hash
106
+ node_config=node_config.symbolize_keys
107
+ # Check mandatory params
108
+ [:url,:username,:password].each { |k| raise CliBadArgument,"missing parameter [#{k}] in node specification: #{node_config}" unless node_config.has_key?(k) }
109
+ if node_config[:password].match(/^Bearer /)
110
+ node_api=Rest.new({
111
+ base_url: node_config[:url],
112
+ headers: {
113
+ 'X-Aspera-AccessKey'=>node_config[:username],
114
+ 'Authorization' =>node_config[:password]}})
115
+ else
116
+ node_api=Rest.new({
117
+ base_url: node_config[:url],
118
+ auth: {
119
+ type: :basic,
120
+ username: node_config[:username],
121
+ password: node_config[:password]
122
+ }})
95
123
  end
96
- node_api=Rest.new({
97
- :base_url => sym_config[:url],
98
- :auth => {
99
- :type =>:basic,
100
- :username => sym_config[:username],
101
- :password => sym_config[:password]
102
- }})
103
124
  new_agent=Fasp::Node.new(node_api)
125
+ # add root id if it's an access key
126
+ new_agent.options={root_id: node_config[:root_id]} if node_config.has_key?(:root_id)
104
127
  when :aoc
105
- aoc_config=@opt_mgr.get_option(:transfer_info,:optional)
128
+ aoc_config=options.get_option(:transfer_info,:optional)
106
129
  if aoc_config.nil?
107
- param_set_name=@config.get_plugin_default_config_name(:aspera)
130
+ param_set_name=config.get_plugin_default_config_name(:aspera)
108
131
  raise CliBadArgument,"No default AoC configured, Please specify --#{:transfer_info.to_s.gsub('_','-')}" if param_set_name.nil?
109
- aoc_config=@config.preset_by_name(param_set_name)
132
+ aoc_config=config.preset_by_name(param_set_name)
110
133
  end
111
134
  Log.log.debug("aoc=#{aoc_config}")
112
135
  raise CliBadArgument,"the aoc configuration shall be Hash, not #{aoc_config.class} (#{aoc_config}), refer to manual" if !aoc_config.is_a?(Hash)
@@ -118,7 +141,7 @@ module Aspera
118
141
  aoc_config[:private_key]=ExtendedValue.instance.evaluate(aoc_config[:private_key])
119
142
  new_agent=Fasp::Aoc.new(aoc_config)
120
143
  else
121
- raise "INTERNAL ERROR"
144
+ raise "Unexpected transfer agent type: #{agent_type}"
122
145
  end
123
146
  set_agent_instance(new_agent)
124
147
  return nil
@@ -128,7 +151,7 @@ module Aspera
128
151
  # sets default if needed
129
152
  # param: 'send' or 'receive'
130
153
  def destination_folder(direction)
131
- dest_folder=@opt_mgr.get_option(:to_folder,:optional)
154
+ dest_folder=options.get_option(:to_folder,:optional)
132
155
  return dest_folder unless dest_folder.nil?
133
156
  dest_folder=@transfer_spec_cmdline['destination_root']
134
157
  return dest_folder unless dest_folder.nil?
@@ -151,12 +174,12 @@ module Aspera
151
174
  # start with lower priority : get paths from transfer spec on command line
152
175
  @transfer_paths=@transfer_spec_cmdline['paths'] if @transfer_spec_cmdline.has_key?('paths')
153
176
  # is there a source list option ?
154
- file_list=@opt_mgr.get_option(:sources,:optional)
177
+ file_list=options.get_option(:sources,:optional)
155
178
  case file_list
156
179
  when nil,FILE_LIST_FROM_ARGS
157
180
  Log.log.debug("getting file list as parameters")
158
181
  # get remaining arguments
159
- file_list=@opt_mgr.get_next_argument("source file list",:multiple)
182
+ file_list=options.get_next_argument("source file list",:multiple)
160
183
  raise CliBadArgument,"specify at least one file on command line or use --sources=#{FILE_LIST_FROM_TRANSFER_SPEC} to use transfer spec" if !file_list.is_a?(Array) or file_list.empty?
161
184
  when FILE_LIST_FROM_TRANSFER_SPEC
162
185
  Log.log.debug("assume list provided in transfer spec")
@@ -173,14 +196,14 @@ module Aspera
173
196
  if !@transfer_paths.nil?
174
197
  Log.log.warn("--sources overrides paths from --ts")
175
198
  end
176
- case @opt_mgr.get_option(:src_type,:mandatory)
199
+ case options.get_option(:src_type,:mandatory)
177
200
  when :list
178
201
  # when providing a list, just specify source
179
202
  @transfer_paths=file_list.map{|i|{'source'=>i}}
180
203
  when :pair
181
- raise CliBadArgument,"whe using pair, provide even number of paths: #{file_list.length}" unless file_list.length.even?
204
+ raise CliBadArgument,"When using pair, provide an even number of paths: #{file_list.length}" unless file_list.length.even?
182
205
  @transfer_paths=file_list.each_slice(2).to_a.map{|s,d|{'source'=>s,'destination'=>d}}
183
- else raise "ERROR"
206
+ else raise "Unsupported src_type"
184
207
  end
185
208
  Log.log.debug("paths=#{@transfer_paths}")
186
209
  return @transfer_paths
@@ -188,20 +211,20 @@ module Aspera
188
211
 
189
212
  # start a transfer and wait for completion, plugins shall use this method
190
213
  # @param transfer_spec
191
- # @param options specific options for the transfer_agent
192
- # options[:src] specifies how destination_root is set (how transfer spec was generated)
214
+ # @param tr_opts specific options for the transfer_agent
215
+ # tr_opts[:src] specifies how destination_root is set (how transfer spec was generated)
193
216
  # other options are carried to specific agent
194
- def start(transfer_spec,options)
217
+ def start(transfer_spec,tr_opts)
195
218
  # check parameters
196
219
  raise "transfer_spec must be hash" unless transfer_spec.is_a?(Hash)
197
- raise "options must be hash" unless options.is_a?(Hash)
220
+ raise "tr_opts must be hash" unless tr_opts.is_a?(Hash)
198
221
  # process :src option
199
222
  case transfer_spec['direction']
200
223
  when 'receive'
201
224
  # init default if required in any case
202
225
  @transfer_spec_cmdline['destination_root']||=destination_folder(transfer_spec['direction'])
203
226
  when 'send'
204
- case options[:src]
227
+ case tr_opts[:src]
205
228
  when :direct
206
229
  # init default if required
207
230
  @transfer_spec_cmdline['destination_root']||=destination_folder(transfer_spec['direction'])
@@ -212,12 +235,12 @@ module Aspera
212
235
  when :node_gen4
213
236
  @transfer_spec_cmdline.delete('destination_root') if @transfer_spec_cmdline.has_key?('destination_root_id')
214
237
  else
215
- raise StandardError,"InternalError: unsupported value: #{options[:src]}"
238
+ raise StandardError,"InternalError: unsupported value: #{tr_opts[:src]}"
216
239
  end
217
240
  end
218
241
 
219
242
  # only used here
220
- options.delete(:src)
243
+ tr_opts.delete(:src)
221
244
 
222
245
  # update command line paths, unless destination already has one
223
246
  @transfer_spec_cmdline['paths']=transfer_spec['paths'] || ts_source_paths
@@ -226,13 +249,26 @@ module Aspera
226
249
  # create transfer agent
227
250
  self.set_agent_by_options
228
251
  Log.log.debug("transfer agent is a #{@agent.class}")
229
- @agent.start_transfer(transfer_spec,options)
252
+ @agent.start_transfer(transfer_spec,tr_opts)
230
253
  result=@agent.wait_for_transfers_completion
231
254
  @progress_listener.reset
232
255
  Fasp::Manager.validate_status_list(result)
256
+ send_email_transfer_notification(transfer_spec,result)
233
257
  return result
234
258
  end
235
259
 
260
+ def send_email_transfer_notification(transfer_spec,statuses)
261
+ return if options.get_option(:notif_to,:optional).nil?
262
+ global_status=self.class.session_status(statuses)
263
+ email_vars={
264
+ global_transfer_status: global_status,
265
+ subject: "ascli transfer: #{global_status}",
266
+ body: "Transfer is: #{global_status}",
267
+ ts: transfer_spec
268
+ }
269
+ @env[:config].send_email_template(email_vars,DEFAULT_TRANSFER_NOTIF_TMPL)
270
+ end
271
+
236
272
  # @return :success if all sessions statuses returned by "start" are success
237
273
  # else return the first error exception object
238
274
  def self.session_status(statuses)
@@ -1,5 +1,5 @@
1
1
  module Aspera
2
2
  module Cli
3
- VERSION = "4.1.0"
3
+ VERSION = "4.3.0"
4
4
  end
5
5
  end