aspera-cli 4.0.0 → 4.2.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (50) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +843 -304
  3. data/bin/dascli +13 -0
  4. data/docs/Makefile +4 -4
  5. data/docs/README.erb.md +805 -172
  6. data/docs/test_env.conf +22 -3
  7. data/examples/aoc.rb +14 -3
  8. data/examples/faspex4.rb +89 -0
  9. data/lib/aspera/aoc.rb +87 -108
  10. data/lib/aspera/cli/formater.rb +2 -0
  11. data/lib/aspera/cli/main.rb +89 -49
  12. data/lib/aspera/cli/plugin.rb +9 -4
  13. data/lib/aspera/cli/plugins/alee.rb +1 -1
  14. data/lib/aspera/cli/plugins/aoc.rb +188 -173
  15. data/lib/aspera/cli/plugins/ats.rb +2 -2
  16. data/lib/aspera/cli/plugins/config.rb +218 -145
  17. data/lib/aspera/cli/plugins/console.rb +2 -2
  18. data/lib/aspera/cli/plugins/faspex.rb +114 -61
  19. data/lib/aspera/cli/plugins/faspex5.rb +85 -43
  20. data/lib/aspera/cli/plugins/node.rb +3 -3
  21. data/lib/aspera/cli/plugins/preview.rb +59 -45
  22. data/lib/aspera/cli/plugins/server.rb +23 -8
  23. data/lib/aspera/cli/transfer_agent.rb +77 -49
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/command_line_builder.rb +49 -31
  26. data/lib/aspera/cos_node.rb +33 -28
  27. data/lib/aspera/environment.rb +2 -2
  28. data/lib/aspera/fasp/connect.rb +28 -21
  29. data/lib/aspera/fasp/http_gw.rb +140 -28
  30. data/lib/aspera/fasp/installation.rb +93 -46
  31. data/lib/aspera/fasp/local.rb +88 -45
  32. data/lib/aspera/fasp/manager.rb +15 -0
  33. data/lib/aspera/fasp/node.rb +4 -4
  34. data/lib/aspera/fasp/parameters.rb +59 -101
  35. data/lib/aspera/fasp/parameters.yaml +531 -0
  36. data/lib/aspera/fasp/resume_policy.rb +13 -12
  37. data/lib/aspera/fasp/uri.rb +1 -1
  38. data/lib/aspera/log.rb +1 -1
  39. data/lib/aspera/node.rb +61 -1
  40. data/lib/aspera/oauth.rb +49 -46
  41. data/lib/aspera/persistency_folder.rb +9 -4
  42. data/lib/aspera/preview/file_types.rb +53 -21
  43. data/lib/aspera/preview/generator.rb +3 -3
  44. data/lib/aspera/rest.rb +29 -18
  45. data/lib/aspera/secrets.rb +20 -0
  46. data/lib/aspera/sync.rb +40 -35
  47. data/lib/aspera/temp_file_manager.rb +19 -0
  48. data/lib/aspera/web_auth.rb +105 -0
  49. metadata +54 -20
  50. data/docs/transfer_spec.html +0 -99
@@ -4,21 +4,46 @@ module Aspera
4
4
  # process_param is called repeatedly with all known parameters
5
5
  # add_env_args is called to get resulting param list and env var (also checks that all params were used)
6
6
  class CommandLineBuilder
7
+ # transform yes/no to trye/false
8
+ def self.yes_to_true(value)
9
+ case value
10
+ when 'yes'; return true
11
+ when 'no'; return false
12
+ end
13
+ raise "unsupported value: #{value}"
14
+ end
7
15
 
8
- private
9
- # default value for command line based on option name
10
- def switch_name(param_name,options)
11
- return options[:option_switch] if options.has_key?(:option_switch)
12
- return '--'+param_name.to_s.gsub('_','-')
16
+ # Called by provider of definition before constructor of this class so that params_definition has all mandatory fields
17
+ def self.normalize_description(d)
18
+ d.each do |param_name,options|
19
+ Log.log.debug("def: #{param_name}")
20
+ raise "Expecting Hash, but have #{options.class} in #{param_name}" unless options.is_a?(Hash)
21
+ #options[:accepted_types]=:bool if options[:cltype].eql?(:envvar) and !options.has_key?(:accepted_types)
22
+ # by default : not mandatory
23
+ options[:mandatory]||=false
24
+ options[:desc]||=''
25
+ # by default : string, unless it's without arg
26
+ if ! options.has_key?(:accepted_types)
27
+ options[:accepted_types]=options[:cltype].eql?(:opt_without_arg) ? :bool : :string
28
+ end
29
+ # single type is placed in array
30
+ options[:accepted_types]=[options[:accepted_types]] unless options[:accepted_types].is_a?(Array)
31
+ if !options.has_key?(:option_switch) and options.has_key?(:cltype) and [:opt_without_arg,:opt_with_arg].include?(options[:cltype])
32
+ options[:option_switch]='--'+param_name.to_s.gsub('_','-')
33
+ end
34
+ end
13
35
  end
14
36
 
37
+ private
38
+
39
+ # clvarname : command line variable name
15
40
  def env_name(param_name,options)
16
- return options[:variable]
41
+ return options[:clvarname]
17
42
  end
18
43
 
19
44
  public
20
45
 
21
- BOOLEAN_CLASSES=[TrueClass,FalseClass]
46
+ attr_reader :params_definition
22
47
 
23
48
  # @param param_hash
24
49
  def initialize(param_hash,params_definition)
@@ -44,15 +69,6 @@ module Aspera
44
69
  return nil
45
70
  end
46
71
 
47
- # transform yes/no to trye/false
48
- def self.yes_to_true(value)
49
- case value
50
- when 'yes'; return true
51
- when 'no'; return false
52
- end
53
- raise "unsupported value: #{value}"
54
- end
55
-
56
72
  # add options directly to ascp command line
57
73
  def add_command_line_options(options)
58
74
  return if options.nil?
@@ -71,24 +87,25 @@ module Aspera
71
87
  # @param options : options for type
72
88
  def process_param(param_name,action=nil)
73
89
  options=@params_definition[param_name]
74
- action=options[:type] if action.nil?
90
+ action=options[:cltype] if action.nil?
75
91
  # should not happen
76
92
  raise "Internal error: ask processing of param #{param_name}" if options.nil?
77
- # by default : not mandatory
78
- options[:mandatory]||=false
79
- if options.has_key?(:accepted_types)
80
- # single type is placed in array
81
- options[:accepted_types]=[options[:accepted_types]] unless options[:accepted_types].is_a?(Array)
82
- else
83
- # by default : string, unless it's without arg
84
- options[:accepted_types]=action.eql?(:opt_without_arg) ? BOOLEAN_CLASSES : [String]
85
- end
86
93
  # check mandatory parameter (nil is valid value)
87
94
  raise Fasp::Error.new("mandatory parameter: #{param_name}") if options[:mandatory] and !@param_hash.has_key?(param_name)
88
95
  parameter_value=@param_hash[param_name]
89
- parameter_value=options[:default] if parameter_value.nil? and options.has_key?(:default)
96
+ #parameter_value=options[:default] if parameter_value.nil? and options.has_key?(:default)
97
+ expected_classes=options[:accepted_types].map do |s|
98
+ case s
99
+ when :string; String
100
+ when :array; Array
101
+ when :hash; Hash
102
+ when :int; Integer
103
+ when :bool; [TrueClass,FalseClass]
104
+ else raise "INTERNAL: unexpected value: #{s}"
105
+ end
106
+ end.flatten
90
107
  # check provided type
91
- raise Fasp::Error.new("#{param_name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, ") unless parameter_value.nil? or options[:accepted_types].inject(false){|m,v|m or parameter_value.is_a?(v)}
108
+ raise Fasp::Error.new("#{param_name} is : #{parameter_value.class} (#{parameter_value}), shall be #{options[:accepted_types]}, ") unless parameter_value.nil? or expected_classes.include?(parameter_value.class)
92
109
  @used_param_names.push(param_name) unless action.eql?(:defer)
93
110
 
94
111
  # process only non-nil values
@@ -102,7 +119,8 @@ module Aspera
102
119
  end
103
120
  raise "unsupported value: #{parameter_value}" unless options[:accepted_values].nil? or options[:accepted_values].include?(parameter_value)
104
121
  if options[:encode]
105
- newvalue=options[:encode].call(parameter_value)
122
+ # :encode has name of class with encoding method
123
+ newvalue=Kernel.const_get(options[:encode]).send("encode_#{param_name}",parameter_value)
106
124
  raise Fasp::Error.new("unsupported #{param_name}: #{parameter_value}") if newvalue.nil?
107
125
  parameter_value=newvalue
108
126
  end
@@ -123,12 +141,12 @@ module Aspera
123
141
  else raise Fasp::Error.new("unsupported #{param_name}: #{parameter_value}")
124
142
  end
125
143
  add_param=!add_param if options[:add_on_false]
126
- add_command_line_options([switch_name(param_name,options)]) if add_param
144
+ add_command_line_options([options[:option_switch]]) if add_param
127
145
  when :opt_with_arg # transform into command line option with value
128
146
  #parameter_value=parameter_value.to_s if parameter_value.is_a?(Integer)
129
147
  parameter_value=[parameter_value] unless parameter_value.is_a?(Array)
130
148
  # if transfer_spec value is an array, applies option many times
131
- parameter_value.each{|v|add_command_line_options([switch_name(param_name,options),v])}
149
+ parameter_value.each{|v|add_command_line_options([options[:option_switch],v])}
132
150
  else
133
151
  raise "Error"
134
152
  end
@@ -5,46 +5,51 @@ require 'xmlsimple'
5
5
  module Aspera
6
6
  class CosNode < Rest
7
7
  attr_reader :add_ts
8
- def initialize(bucket_name,storage_endpoint,instance_id,api_key,auth_url='https://iam.cloud.ibm.com/identity')
8
+ IBM_CLOUD_TOKEN_URL='https://iam.cloud.ibm.com/identity'
9
+ def initialize(bucket_name,storage_endpoint,instance_id,api_key,auth_url=IBM_CLOUD_TOKEN_URL)
10
+ @auth_url=auth_url
11
+ @api_key=api_key
9
12
  s3_api=Aspera::Rest.new({
10
- :base_url => storage_endpoint,
11
- :not_auth_codes => ['401','403'],
12
- :headers => {'ibm-service-instance-id' => instance_id},
13
- :auth => {
14
- :type => :oauth2,
15
- :base_url => auth_url,
16
- :grant => :ibm_apikey,
17
- :api_key => api_key
13
+ :base_url => storage_endpoint,
14
+ :not_auth_codes => ['401','403'], # error codes when not authorized
15
+ :headers => {'ibm-service-instance-id' => instance_id},
16
+ :auth => {
17
+ :type => :oauth2,
18
+ :base_url => @auth_url,
19
+ :grant => :ibm_apikey,
20
+ :api_key => @api_key
18
21
  }})
19
22
  # read FASP connection information for bucket
20
23
  xml_result_text=s3_api.call({:operation=>'GET',:subpath=>bucket_name,:headers=>{'Accept'=>'application/xml'},:url_params=>{'faspConnectionInfo'=>nil}})[:http].body
21
24
  ats_info=XmlSimple.xml_in(xml_result_text, {'ForceArray' => false})
22
25
  Aspera::Log.dump('ats_info',ats_info)
23
- # get delegated token
24
- delegated_oauth=Oauth.new({
25
- :type => :oauth2,
26
- :base_url => auth_url,
27
- :grant => :delegated_refresh,
28
- :api_key => api_key,
29
- :token_field=> 'delegated_refresh_token'
30
- })
31
- # to be placed in rest call header and in transfer tags
32
- aspera_storage_credentials={
33
- 'type' => 'token',
34
- 'token' => {'delegated_refresh_token'=>delegated_oauth.get_authorization().gsub(/^Bearer /,'')}
35
- }
36
- # transfer spec addition
37
- @add_ts={'tags'=>{'aspera'=>{'node'=>{'storage_credentials'=>aspera_storage_credentials}}}}
38
- # set a general addon to transfer spec
39
- # here we choose to use the add_request_param
40
- #self.transfer.option_transfer_spec_deep_merge(@add_ts)
41
26
  super({
42
27
  :base_url => ats_info['ATSEndpoint'],
43
- :headers => {'X-Aspera-Storage-Credentials'=>JSON.generate(aspera_storage_credentials)},
44
28
  :auth => {
45
29
  :type => :basic,
46
30
  :username => ats_info['AccessKey']['Id'],
47
31
  :password => ats_info['AccessKey']['Secret']}})
32
+ # prepare transfer spec addition
33
+ @add_ts={'tags'=>{'aspera'=>{'node'=>{'storage_credentials'=>{
34
+ 'type' => 'token',
35
+ 'token' => {'delegated_refresh_token'=>nil}
36
+ }}}}}
37
+ generate_token
38
+ end
39
+
40
+ # potentially call this if delegated token is expired
41
+ def generate_token
42
+ # OAuth API to get delegated token
43
+ delegated_oauth=Oauth.new({
44
+ :type => :oauth2,
45
+ :base_url => @auth_url,
46
+ :grant => :delegated_refresh,
47
+ :api_key => @api_key,
48
+ :token_field=> 'delegated_refresh_token'
49
+ })
50
+ # get delagated token to be placed in rest call header and in transfer tags
51
+ @add_ts['tags']['aspera']['node']['storage_credentials']['token']['delegated_refresh_token']=delegated_oauth.get_authorization().gsub(/^Bearer /,'')
52
+ @params[:headers]={'X-Aspera-Storage-Credentials'=>JSON.generate(@add_ts['tags']['aspera']['node']['storage_credentials'])}
48
53
  end
49
54
  end
50
55
  end
@@ -2,7 +2,7 @@ require 'aspera/log'
2
2
  require 'rbconfig'
3
3
 
4
4
  module Aspera
5
- # a simple binary data repository
5
+ # detect OS, architecture, and OS specific stuff
6
6
  class Environment
7
7
  OS_WINDOWS = :windows
8
8
  OS_X = :osx
@@ -53,7 +53,7 @@ module Aspera
53
53
  return ''
54
54
  end
55
55
 
56
- # on Windows, the env var %USERPROFILE% provides the path to user's home more reliably then %HOMEDRIVE%%HOMEPATH%
56
+ # on Windows, the env var %USERPROFILE% provides the path to user's home more reliably than %HOMEDRIVE%%HOMEPATH%
57
57
  def self.fix_home
58
58
  if os.eql?(OS_WINDOWS)
59
59
  if ENV.has_key?('USERPROFILE') and Dir.exist?(ENV['USERPROFILE'])
@@ -12,7 +12,9 @@ module Aspera
12
12
  private_constant :MAX_CONNECT_START_RETRY,:SLEEP_SEC_BETWEEN_RETRY
13
13
  def initialize
14
14
  super
15
- @connect_app_id=SecureRandom.uuid
15
+ @connect_settings={
16
+ 'app_id' => SecureRandom.uuid
17
+ }
16
18
  # TODO: start here and create monitor
17
19
  end
18
20
 
@@ -32,46 +34,51 @@ module Aspera
32
34
  OpenApplication.uri_graphical('https://downloads.asperasoft.com/connect2/')
33
35
  raise StandardError,'Connect is not installed'
34
36
  end
35
- sleep SLEEP_SEC_BETWEEN_RETRY
37
+ sleep(SLEEP_SEC_BETWEEN_RETRY)
36
38
  retry
37
39
  end
38
40
  if transfer_spec['direction'] == 'send'
39
41
  Log.log.warn("Connect requires upload selection using GUI, ignoring #{transfer_spec['paths']}".red)
40
42
  transfer_spec.delete('paths')
41
- resdata=@connect_api.create('windows/select-open-file-dialog/',{'title'=>'Select Files','suggestedName'=>'','allowMultipleSelection'=>true,'allowedFileTypes'=>'','aspera_connect_settings'=>{'app_id'=>@connect_app_id}})[:data]
43
+ resdata=@connect_api.create('windows/select-open-file-dialog/',{'aspera_connect_settings'=>@connect_settings,'title'=>'Select Files','suggestedName'=>'','allowMultipleSelection'=>true,'allowedFileTypes'=>''})[:data]
42
44
  transfer_spec['paths']=resdata['dataTransfer']['files'].map { |i| {'source'=>i['name']}}
43
45
  end
44
46
  @request_id=SecureRandom.uuid
45
47
  # if there is a token, we ask connect client to use well known ssh private keys
46
48
  # instead of asking password
47
49
  transfer_spec['authentication']='token' if transfer_spec.has_key?('token')
50
+ connect_settings=
48
51
  connect_transfer_args={
49
- 'transfer_specs'=>[{
50
- 'transfer_spec'=>transfer_spec,
51
- 'aspera_connect_settings'=>{
52
- 'allow_dialogs'=>true,
53
- 'app_id'=>@connect_app_id,
54
- 'request_id'=>@request_id
55
- }}]}
52
+ 'aspera_connect_settings'=>@connect_settings.merge({
53
+ 'request_id' =>@request_id,
54
+ 'allow_dialogs' =>true,
55
+ }),
56
+ 'transfer_specs' =>[{
57
+ 'transfer_spec' =>transfer_spec,
58
+ }]}
56
59
  # asynchronous anyway
57
- @connect_api.create('transfers/start',connect_transfer_args)
60
+ res=@connect_api.create('transfers/start',connect_transfer_args)[:data]
61
+ @xfer_id=res['transfer_specs'].first['transfer_spec']['tags']['aspera']['xfer_id']
58
62
  end
59
63
 
60
64
  def wait_for_transfers_completion
61
- connect_activity_args={'aspera_connect_settings'=>{'app_id'=>@connect_app_id}}
65
+ connect_activity_args={'aspera_connect_settings'=>@connect_settings}
62
66
  started=false
63
67
  spinner=nil
64
68
  loop do
65
- result=@connect_api.create('transfers/activity',connect_activity_args)[:data]
66
- if result['transfers']
67
- trdata=result['transfers'].select{|i| i['aspera_connect_settings'] and i['aspera_connect_settings']['request_id'].eql?(@request_id)}.first
68
- raise 'problem with connect, please kill it' unless trdata
69
+ tr_info=@connect_api.create("transfers/info/#{@xfer_id}",connect_activity_args)[:data]
70
+ if tr_info['transfer_info'].is_a?(Hash)
71
+ trdata=tr_info['transfer_info']
72
+ if trdata.nil?
73
+ Log.log.warn("no session in Connect")
74
+ break
75
+ end
69
76
  # TODO: get session id
70
77
  case trdata['status']
71
78
  when 'completed'
72
- notify_listeners('emulated',{Manager::LISTENER_SESSION_ID_B=>@connect_app_id,'Type'=>'DONE'})
79
+ notify_end(@connect_settings['app_id'])
73
80
  break
74
- when 'initiating'
81
+ when 'initiating','queued'
75
82
  if spinner.nil?
76
83
  spinner = TTY::Spinner.new('[:spinner] :title', format: :classic)
77
84
  spinner.start
@@ -81,13 +88,13 @@ module Aspera
81
88
  when 'running'
82
89
  #puts "running: sessions:#{trdata['sessions'].length}, #{trdata['sessions'].map{|i| i['bytes_transferred']}.join(',')}"
83
90
  if !started and trdata['bytes_expected'] != 0
84
- notify_listeners('emulated',{Manager::LISTENER_SESSION_ID_B=>@connect_app_id,'Type'=>'NOTIFICATION','PreTransferBytes'=>trdata['bytes_expected']})
91
+ notify_begin(@connect_settings['app_id'],trdata['bytes_expected'])
85
92
  started=true
86
93
  else
87
- notify_listeners('emulated',{Manager::LISTENER_SESSION_ID_B=>@connect_app_id,'Type'=>'STATS','Bytescont'=>trdata['bytes_written']})
94
+ notify_progress(@connect_settings['app_id'],trdata['bytes_written'])
88
95
  end
89
96
  else
90
- raise Fasp::Error.new("#{trdata['status']}: #{trdata['error_desc']}")
97
+ raise Fasp::Error.new("unknown status: #{trdata['status']}: #{trdata['error_desc']}")
91
98
  end
92
99
  end
93
100
  sleep 1
@@ -2,12 +2,147 @@
2
2
  require 'aspera/fasp/manager'
3
3
  require 'aspera/log'
4
4
  require 'aspera/rest'
5
+ require 'websocket-client-simple'
6
+ require 'securerandom'
7
+ require 'openssl'
8
+ require 'base64'
9
+ require 'json'
10
+ require 'uri'
5
11
 
6
12
  # ref: https://api.ibm.com/explorer/catalog/aspera/product/ibm-aspera/api/http-gateway-api/doc/guides-toc
7
13
  module Aspera
8
14
  module Fasp
9
15
  # executes a local "ascp", connects mgt port, equivalent of "Fasp Manager"
10
16
  class HttpGW < Manager
17
+ # message returned by HTTP GW in case of success
18
+ OK_MESSAGE='end upload'
19
+ # refresh rate for progress
20
+ UPLOAD_REFRESH_SEC=0.5
21
+ private_constant :OK_MESSAGE,:UPLOAD_REFRESH_SEC
22
+ # send message on http gw web socket
23
+ def ws_send(ws,type,data)
24
+ ws.send(JSON.generate({type => data}))
25
+ end
26
+
27
+ def upload(transfer_spec)
28
+ # precalculate size
29
+ total_size=0
30
+ # currently, files are sent flat
31
+ source_path=[]
32
+ transfer_spec['paths'].each do |item|
33
+ filepath=item['source']
34
+ item['source']=item['destination']=File.basename(filepath)
35
+ total_size+=item['file_size']=File.size(filepath)
36
+ source_path.push(filepath)
37
+ end
38
+ session_id=SecureRandom.uuid
39
+ ws=::WebSocket::Client::Simple::Client.new
40
+ error=nil
41
+ received=0
42
+ ws.on :message do |msg|
43
+ Log.log.info("ws: message: #{msg.data}")
44
+ message=msg.data
45
+ if message.eql?(OK_MESSAGE)
46
+ received+=1
47
+ else
48
+ message.chomp!
49
+ if message[0].eql?('"') and message[-1].eql?('"')
50
+ error=JSON.parse(Base64.strict_decode64(message.chomp[1..-2]))['message']
51
+ else
52
+ error="expecting quotes in [#{message}]"
53
+ end
54
+ end
55
+ end
56
+ ws.on :error do |e|
57
+ error=e
58
+ end
59
+ ws.on :open do
60
+ Log.log.info("ws: open")
61
+ end
62
+ ws.on :close do
63
+ Log.log.info("ws: close")
64
+ end
65
+ # open web socket to end point
66
+ ws.connect("#{@gw_api.params[:base_url]}/upload")
67
+ while !ws.open? and error.nil? do
68
+ Log.log.info("ws: wait")
69
+ sleep(0.2)
70
+ end
71
+ notify_begin(session_id,total_size)
72
+ ws_send(ws,:transfer_spec,transfer_spec)
73
+ # current file index
74
+ filenum=0
75
+ # aggregate size sent
76
+ sent_bytes=0
77
+ # last progress event
78
+ lastevent=Time.now-1
79
+ transfer_spec['paths'].each do |item|
80
+ # TODO: on destination write same path?
81
+ destination_path=item['source']
82
+ # TODO: get mime type?
83
+ file_mime_type=''
84
+ total=item['file_size']
85
+ # compute total number of slices
86
+ numslices=1+(total-1)/@upload_chunksize
87
+ # current slice index
88
+ slicenum=0
89
+ File.open(source_path[filenum]) do |file|
90
+ while !file.eof? do
91
+ data=file.read(@upload_chunksize)
92
+ slice_data={
93
+ name: destination_path,
94
+ type: file_mime_type,
95
+ size: total,
96
+ data: Base64.strict_encode64(data),
97
+ slice: slicenum,
98
+ total_slices: numslices,
99
+ fileIndex: filenum
100
+ }
101
+ ws_send(ws,:slice_upload, slice_data)
102
+ sent_bytes+=data.length
103
+ currenttime=Time.now
104
+ if (currenttime-lastevent)>UPLOAD_REFRESH_SEC
105
+ notify_progress(session_id,sent_bytes)
106
+ lastevent=currenttime
107
+ end
108
+ slicenum+=1
109
+ raise error unless error.nil?
110
+ end
111
+ end
112
+ filenum+=1
113
+ end
114
+ ws.close
115
+ notify_end(session_id)
116
+ end
117
+
118
+ def download(transfer_spec)
119
+ transfer_spec['zip_required']||=false
120
+ transfer_spec['source_root']||='/'
121
+ # is normally provided by application, like package name
122
+ if !transfer_spec.has_key?('download_name')
123
+ # by default it is the name of first file
124
+ dname=File.basename(transfer_spec['paths'].first['source'])
125
+ # we remove extension
126
+ dname=dname.gsub(/\.@gw_api.*$/,'')
127
+ # ands add indication of number of files if there is more than one
128
+ if transfer_spec['paths'].length > 1
129
+ dname=dname+" #{transfer_spec['paths'].length} Files"
130
+ end
131
+ transfer_spec['download_name']=dname
132
+ end
133
+ creation=@gw_api.create('download',{'transfer_spec'=>transfer_spec})[:data]
134
+ transfer_uuid=creation['url'].split('/').last
135
+ if transfer_spec['zip_required'] or transfer_spec['paths'].length > 1
136
+ # it is a zip file if zip is required or there is more than 1 file
137
+ file_dest=transfer_spec['download_name']+'.zip'
138
+ else
139
+ # it is a plain file if we don't require zip and there is only one file
140
+ file_dest=File.basename(transfer_spec['paths'].first['source'])
141
+ end
142
+ file_dest=File.join(transfer_spec['destination_root'],file_dest)
143
+ @gw_api.call({:operation=>'GET',:subpath=>"download/#{transfer_uuid}",:save_to_file=>file_dest})
144
+ end
145
+
11
146
  # start FASP transfer based on transfer spec (hash table)
12
147
  # note that it is asynchronous
13
148
  # HTTP download only supports file list
@@ -15,37 +150,13 @@ module Aspera
15
150
  raise "GW URL must be set" unless !@gw_api.nil?
16
151
  raise "option: must be hash (or nil)" unless options.is_a?(Hash)
17
152
  raise "paths: must be Array" unless transfer_spec['paths'].is_a?(Array)
153
+ raise "on token based transfer is supported in GW" unless transfer_spec['token'].is_a?(String)
154
+ transfer_spec['authentication']||='token'
18
155
  case transfer_spec['direction']
19
156
  when 'send'
20
- # this is a websocket
21
- raise "error, not implemented"
157
+ upload(transfer_spec)
22
158
  when 'receive'
23
- transfer_spec['zip_required']||=false
24
- transfer_spec['authentication']||='token'
25
- transfer_spec['source_root']||='/'
26
- # is normally provided by application, like package name
27
- if !transfer_spec.has_key?('download_name')
28
- # by default it is the name of first file
29
- dname=File.basename(transfer_spec['paths'].first['source'])
30
- # we remove extension
31
- dname=dname.gsub(/\.@gw_api.*$/,'')
32
- # ands add indication of number of files if there is more than one
33
- if transfer_spec['paths'].length > 1
34
- dname=dname+" #{transfer_spec['paths'].length} Files"
35
- end
36
- transfer_spec['download_name']=dname
37
- end
38
- creation=@gw_api.create('download',{'transfer_spec'=>transfer_spec})[:data]
39
- transfer_uuid=creation['url'].split('/').last
40
- if transfer_spec['zip_required'] or transfer_spec['paths'].length > 1
41
- # it is a zip file if zip is required or there is more than 1 file
42
- file_dest=transfer_spec['download_name']+'.zip'
43
- else
44
- # it is a plain file if we don't require zip and there is only one file
45
- file_dest=File.basename(transfer_spec['paths'].first['source'])
46
- end
47
- file_dest=File.join(transfer_spec['destination_root'],file_dest)
48
- @gw_api.call({:operation=>'GET',:subpath=>"download/#{transfer_uuid}",:save_to_file=>file_dest})
159
+ download(transfer_spec)
49
160
  else
50
161
  raise "error"
51
162
  end
@@ -74,6 +185,7 @@ module Aspera
74
185
  @gw_api=Rest.new({:base_url => params[:url]})
75
186
  api_info = @gw_api.read('info')[:data]
76
187
  Log.log.info("#{api_info}")
188
+ @upload_chunksize=128000 # TODO: configurable ?
77
189
  end
78
190
 
79
191
  end # LocalHttp