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
data/lib/aspera/oauth.rb CHANGED
@@ -1,22 +1,26 @@
1
1
  require 'aspera/open_application'
2
+ require 'aspera/web_auth'
2
3
  require 'base64'
3
4
  require 'date'
4
5
  require 'socket'
5
6
  require 'securerandom'
6
7
 
7
8
  module Aspera
8
- # implement OAuth 2 for the REST client and generate a bearer token
9
+ # Implement OAuth 2 for the REST client and generate a bearer token
9
10
  # call get_authorization() to get a token.
10
11
  # bearer tokens are kept in memory and also in a file cache for later re-use
11
12
  # if a token is expired (api returns 4xx), call again get_authorization({:refresh=>true})
12
13
  class Oauth
13
14
  private
14
15
  # remove 5 minutes to account for time offset (TODO: configurable?)
15
- JWT_NOTBEFORE_OFFSET=300
16
+ JWT_NOTBEFORE_OFFSET_SEC=300
16
17
  # one hour validity (TODO: configurable?)
17
- JWT_EXPIRY_OFFSET=3600
18
+ JWT_EXPIRY_OFFSET_SEC=3600
19
+ # tokens older than 30 minutes will be discarded from cache
20
+ TOKEN_CACHE_EXPIRY_SEC=1800
21
+ # a prefix for persistency of tokens (garbage collect)
18
22
  PERSIST_CATEGORY_TOKEN='token'
19
- private_constant :JWT_NOTBEFORE_OFFSET,:JWT_EXPIRY_OFFSET,:PERSIST_CATEGORY_TOKEN
23
+ private_constant :JWT_NOTBEFORE_OFFSET_SEC,:JWT_EXPIRY_OFFSET_SEC,:PERSIST_CATEGORY_TOKEN,:TOKEN_CACHE_EXPIRY_SEC
20
24
  class << self
21
25
  # OAuth methods supported
22
26
  def auth_types
@@ -28,12 +32,18 @@ module Aspera
28
32
  end
29
33
 
30
34
  def persist_mgr
31
- raise "set persistency manager first" if @persist.nil?
35
+ if @persist.nil?
36
+ Log.log.warn('Not using persistency (use Aspera::Oauth.persist_mgr=Aspera::PersistencyFolder.new)')
37
+ # create NULL persistency class
38
+ @persist=Class.new do
39
+ def get(x);nil;end;def delete(x);nil;end;def put(x,y);nil;end;def garbage_collect(x,y);nil;end
40
+ end.new
41
+ end
32
42
  return @persist
33
43
  end
34
44
 
35
45
  def flush_tokens
36
- persist_mgr.flush_by_prefix(PERSIST_CATEGORY_TOKEN)
46
+ persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN,nil)
37
47
  end
38
48
  end
39
49
 
@@ -79,13 +89,19 @@ module Aspera
79
89
  raise "redirect_uri must have a port" if uri.port.nil?
80
90
  # we could check that host is localhost or local address
81
91
  end
92
+ # cleanup expired tokens
93
+ self.class.persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN,TOKEN_CACHE_EXPIRY_SEC)
82
94
  end
83
95
 
84
- THANK_YOU_HTML = "<html><head><title>Ok</title></head><body><h1>Thank you !</h1><p>You can close this window.</p></body></html>"
85
-
86
96
  # open the login page, wait for code and check_code, then return code
87
97
  def goto_page_and_get_code(login_page_url,check_code)
88
- request_params=self.class.goto_page_and_get_request(@params[:redirect_uri],login_page_url)
98
+ Log.log.info("login_page_url=#{login_page_url}".bg_red.gray)
99
+ # start a web server to receive request code
100
+ webserver=WebAuth.new(@params[:redirect_uri])
101
+ # start browser on login page
102
+ OpenApplication.instance.uri(login_page_url)
103
+ # wait for code in request
104
+ request_params=webserver.get_request
89
105
  Log.log.error("state does not match") if !check_code.eql?(request_params['state'])
90
106
  code=request_params['code']
91
107
  return code
@@ -117,6 +133,9 @@ module Aspera
117
133
 
118
134
  public
119
135
 
136
+ # used to change parameter, such as scope
137
+ attr_reader :params
138
+
120
139
  # @param options : :scope and :refresh
121
140
  def get_authorization(options={})
122
141
  # api scope can be overriden to get auth for other scope
@@ -184,14 +203,13 @@ module Aspera
184
203
  when :web
185
204
  # AoC Web based Auth
186
205
  check_code=SecureRandom.uuid
187
- login_page_url=Rest.build_uri(
188
- "#{@params[:base_url]}/#{@params[:path_authorize]}",
189
- p_client_id_and_scope.merge({
206
+ auth_params=p_client_id_and_scope.merge({
190
207
  :response_type => 'code',
191
208
  :redirect_uri => @params[:redirect_uri],
192
- :client_secret => @params[:client_secret],
193
209
  :state => check_code
194
- }))
210
+ })
211
+ auth_params[:client_secret]=@params[:client_secret] if @params.has_key?(:client_secret)
212
+ login_page_url=Rest.build_uri("#{@params[:base_url]}/#{@params[:path_authorize]}",auth_params)
195
213
  # here, we need a human to authorize on a web page
196
214
  code=goto_page_and_get_code(login_page_url,check_code)
197
215
  # exchange code for token
@@ -211,9 +229,19 @@ module Aspera
211
229
  :iss => @params[:client_id], # issuer
212
230
  :sub => @params[:jwt_subject], # subject
213
231
  :aud => @params[:jwt_audience], # audience
214
- :nbf => seconds_since_epoch-JWT_NOTBEFORE_OFFSET, # not before
215
- :exp => seconds_since_epoch+JWT_EXPIRY_OFFSET # expiration
232
+ :nbf => seconds_since_epoch-JWT_NOTBEFORE_OFFSET_SEC, # not before
233
+ :exp => seconds_since_epoch+JWT_EXPIRY_OFFSET_SEC # expiration
216
234
  }
235
+ # Hum.. compliant ? TODO: remove when Faspex5 API is clarified
236
+ if @params.has_key?(:f5_username)
237
+ payload[:jti] = SecureRandom.uuid # JWT id
238
+ payload[:iat] = seconds_since_epoch # issued at
239
+ payload.delete(:nbf)
240
+ p_scope[:redirect_uri]="https://127.0.0.1:5000/token"
241
+ p_scope[:state]=SecureRandom.uuid
242
+ p_scope[:client_id]=@params[:client_id]
243
+ @token_auth_api.params[:auth]={type: :basic,username: @params[:f5_username], password: @params[:f5_password]}
244
+ end
217
245
 
218
246
  # non standard, only for global ids
219
247
  payload.merge!(@params[:jwt_add]) if @params.has_key?(:jwt_add)
@@ -222,8 +250,8 @@ module Aspera
222
250
 
223
251
  Log.log.debug("private=[#{rsa_private}]")
224
252
 
225
- Log.log.debug("JWT assertion=[#{payload}]")
226
- assertion = JWT.encode(payload, rsa_private, 'RS256')
253
+ Log.log.debug("JWT payload=[#{payload}]")
254
+ assertion = JWT.encode(payload, rsa_private, 'RS256',@params[:jwt_headers]||{})
227
255
 
228
256
  Log.log.debug("assertion=[#{assertion}]")
229
257
 
@@ -233,8 +261,10 @@ module Aspera
233
261
  }))
234
262
  when :url_token
235
263
  # AoC Public Link
264
+ params={:url_token=>@params[:url_token]}
265
+ params[:password]=@params[:password] if @params.has_key?(:password)
236
266
  resp=create_token_advanced({
237
- :json_params => {:url_token=>@params[:url_token]},
267
+ :json_params => params,
238
268
  :url_params => p_scope.merge({
239
269
  :grant_type => 'url_token'
240
270
  })})
@@ -288,32 +318,5 @@ module Aspera
288
318
  return 'Bearer '+token_data[@params[:token_field]]
289
319
  end
290
320
 
291
- # open the login page, wait for code and return parameters
292
- def self.goto_page_and_get_request(redirect_uri,login_page_url,html_page=THANK_YOU_HTML)
293
- Log.log.info "login_page_url=#{login_page_url}".bg_red().gray()
294
- # browser start is not blocking, we hope here that starting is slower than opening port
295
- OpenApplication.instance.uri(login_page_url)
296
- port=URI.parse(redirect_uri).port
297
- Log.log.info "listening on port #{port}"
298
- request_params=nil
299
- TCPServer.open('127.0.0.1', port) { |webserver|
300
- Log.log.info "server=#{webserver}"
301
- websession = webserver.accept
302
- sleep 1 # TODO: sometimes: returns nil ? use webrick ?
303
- line = websession.gets.chomp
304
- Log.log.info "line=#{line}"
305
- if ! line.start_with?('GET /?') then
306
- raise "unexpected request"
307
- end
308
- request = line.partition('?').last.partition(' ').first
309
- data=URI.decode_www_form(request)
310
- request_params=data.to_h
311
- Log.log.debug "request_params=#{request_params}"
312
- websession.print "HTTP/1.1 200/OK\r\nContent-type:text/html\r\n\r\n#{html_page}"
313
- websession.close
314
- }
315
- return request_params
316
- end
317
-
318
321
  end # OAuth
319
322
  end # Aspera
@@ -55,12 +55,17 @@ module Aspera
55
55
  @cache.delete(object_id)
56
56
  end
57
57
 
58
- def flush_by_prefix(persist_category)
59
- persist_files=Dir[File.join(@folder,persist_category+'*'+FILE_SUFFIX)]
60
- persist_files.each do |filepath|
58
+ def garbage_collect(persist_category,max_age_seconds=nil)
59
+ garbage_files=Dir[File.join(@folder,persist_category+'*'+FILE_SUFFIX)]
60
+ if !max_age_seconds.nil?
61
+ current_time = Time.now
62
+ garbage_files.select! { |filepath| (current_time - File.stat(filepath).mtime).to_i > max_age_seconds}
63
+ end
64
+ garbage_files.each do |filepath|
61
65
  File.delete(filepath)
66
+ Log.log.debug("Deleted expired: #{filepath}")
62
67
  end
63
- return persist_files
68
+ return garbage_files
64
69
  end
65
70
 
66
71
  private
@@ -1,9 +1,11 @@
1
- require 'mimemagic'
2
- require 'mimemagic/overlay'
1
+ require 'aspera/log'
2
+ require 'singleton'
3
3
 
4
4
  module Aspera
5
5
  module Preview
6
+ # function conversion_type returns one of the types: CONVERSION_TYPES
6
7
  class FileTypes
8
+ include Singleton
7
9
  # values for conversion_type : input format
8
10
  CONVERSION_TYPES=[
9
11
  :image,
@@ -148,6 +150,7 @@ module Aspera
148
150
  'dif' => :office,
149
151
  'divx' => :video,
150
152
  'dng' => :image,
153
+ 'docx' => :office,
151
154
  'dpx' => :image,
152
155
  'epdf' => :image,
153
156
  'epi' => :image,
@@ -204,6 +207,7 @@ module Aspera
204
207
  'pam' => :image,
205
208
  'pcd' => :image,
206
209
  'pcds' => :image,
210
+ 'pdf' => :pdf,
207
211
  'pef' => :image,
208
212
  'picon' => :image,
209
213
  'pict' => :image,
@@ -261,39 +265,67 @@ module Aspera
261
265
  'x3f' => :image,
262
266
  'xcf' => :image,
263
267
  'xlk' => :office,
268
+ 'xlsx' => :office,
269
+ 'xls' => :office,
264
270
  'ycbcr' => :image,
265
271
  'ycbcra' => :image,
266
272
  'yuv' => :image,
267
273
  'zabw' => :office}
268
274
 
269
- #private_constant :SUPPORTED_MIME_TYPES, :SUPPORTED_EXTENSIONS
275
+ private_constant :SUPPORTED_MIME_TYPES, :SUPPORTED_EXTENSIONS
270
276
 
271
- def self.mime_from_file(filepath)
277
+ # @attr use_mimemagic [bool] true to use mimemagic to determine real mime type based on file content
278
+ attr_accessor :use_mimemagic
279
+
280
+ def initialize
281
+ @use_mimemagic=false
282
+ end
283
+
284
+ # use mime magic to find mime type based on file content (magic numbers)
285
+ def mime_from_file(filepath)
286
+ # moved here, as mimemagic can cause installation issues
287
+ require 'mimemagic'
288
+ require 'mimemagic/version'
289
+ require 'mimemagic/overlay' if MimeMagic::VERSION.start_with?('0.3.')
290
+ # check magic number inside file (empty string if not found)
272
291
  detected_mime=MimeMagic.by_magic(File.open(filepath)).to_s
273
- detected_mime=MimeMagic.by_path(filepath).to_s if detected_mime.empty?
292
+ # check extension only
293
+ if !SUPPORTED_MIME_TYPES.has_key?(detected_mime)
294
+ Log.log.debug("no conversion for #{detected_mime}, trying extension")
295
+ detected_mime=MimeMagic.by_extension(File.extname(filepath)).to_s
296
+ end
274
297
  detected_mime=nil if detected_mime.empty?
298
+ Log.log.debug("mimemagic: #{detected_mime.class.name} [#{detected_mime}]")
275
299
  return detected_mime
276
300
  end
277
301
 
278
- def self.conversion_type(filepath,mimetype,try_local_mime)
279
- detected_mime=nil
280
- if try_local_mime
302
+ # return file type, one of enum CONVERSION_TYPES
303
+ # @param filepath [String] full path to file
304
+ # @param mimetype [String] provided by node api
305
+ def conversion_type(filepath,mimetype)
306
+ Log.log.debug("conversion_type(#{filepath},m=#{mimetype},t=#{@use_mimemagic})")
307
+ # 1- get type from provided mime type, using local mapping
308
+ conv_type=SUPPORTED_MIME_TYPES[mimetype] if ! mimetype.nil?
309
+ # 2- else, from computed mime type (if available)
310
+ if conv_type.nil? and @use_mimemagic
281
311
  detected_mime=mime_from_file(filepath)
282
- if mimetype.eql?(detected_mime)
283
- Log.log.debug("matching mime type per magic number")
284
- else
285
- # note: detected can be nil
286
- Log.log.debug("non matching mime types: node=[#{mimetype}], magic=[#{detected_mime}]")
312
+ if ! detected_mime.nil?
313
+ conv_type=SUPPORTED_MIME_TYPES[detected_mime]
314
+ if ! mimetype.nil?
315
+ if mimetype.eql?(detected_mime)
316
+ Log.log.debug("matching mime type per magic number")
317
+ else
318
+ # note: detected can be nil
319
+ Log.log.debug("non matching mime types: node=[#{mimetype}], magic=[#{detected_mime}]")
320
+ end
321
+ end
287
322
  end
288
323
  end
289
- # 1- get type from provided mime type
290
- result=FileTypes::SUPPORTED_MIME_TYPES[mimetype] unless mimetype.nil?
291
- # 2- else, from computed mime type
292
- result||=FileTypes::SUPPORTED_MIME_TYPES[detected_mime] unless detected_mime.nil?
293
- extension = File.extname(filepath).downcase.gsub(/^\./,'')
294
- # 3- else, from local extensions
295
- result||=FileTypes::SUPPORTED_EXTENSIONS[extension]
296
- return result
324
+ # 3- else, from extensions, using local mapping
325
+ extension = File.extname(filepath.downcase)[1..-1]
326
+ conv_type=SUPPORTED_EXTENSIONS[extension] if conv_type.nil?
327
+ Log.log.debug("conversion_type(#{extension}): #{conv_type.class.name} [#{conv_type}]")
328
+ return conv_type
297
329
  end
298
330
  end
299
331
  end
@@ -15,7 +15,7 @@ module Aspera
15
15
 
16
16
  # @param src source file path
17
17
  # @param dst destination file path
18
- # @param api_mime_type optional mime type as provided by node api
18
+ # @param api_mime_type optional mime type as provided by node api (or nil)
19
19
  # node API mime types are from: http://svn.apache.org/repos/asf/httpd/httpd/trunk/docs/conf/mime.types
20
20
  # supported preview type is one of Preview::PREVIEW_FORMATS
21
21
  # the resulting preview file type is taken from destination file extension.
@@ -25,14 +25,14 @@ module Aspera
25
25
  # -> preview_format is one of Generator::PREVIEW_FORMATS
26
26
  # the conversion video->mp4 is implemented in methods: convert_video_to_mp4_using_<video_conversion>
27
27
  # -> conversion method is one of Generator::VIDEO_CONVERSION_METHODS
28
- def initialize(options,src,dst,main_temp_dir,api_mime_type=nil,try_local_mime=true)
28
+ def initialize(options,src,dst,main_temp_dir,api_mime_type)
29
29
  @options=options
30
30
  @source_file_path=src
31
31
  @destination_file_path=dst
32
32
  @temp_folder=File.join(main_temp_dir,@source_file_path.split('/').last.gsub(/\s/, '_').gsub(/\W/, ''))
33
33
  # extract preview format from extension of target file
34
34
  @preview_format_symb=File.extname(@destination_file_path).gsub(/^\./,'').to_sym
35
- @conversion_type=FileTypes.conversion_type(@source_file_path,api_mime_type,try_local_mime)
35
+ @conversion_type=FileTypes.instance.conversion_type(@source_file_path,api_mime_type)
36
36
  end
37
37
 
38
38
  # name of processing method in this object
data/lib/aspera/rest.rb CHANGED
@@ -7,6 +7,7 @@ require 'net/http'
7
7
  require 'net/https'
8
8
  require 'json'
9
9
  require 'base64'
10
+ require 'cgi'
10
11
  require 'ruby-progressbar'
11
12
 
12
13
  # add cancel method to http
@@ -33,6 +34,7 @@ module Aspera
33
34
  # true if https ignore certificate
34
35
  @@insecure=false
35
36
  @@user_agent='Ruby'
37
+ @@download_partial_suffix='.http_partial'
36
38
 
37
39
  public
38
40
  def self.insecure=(v); @@insecure=v;Log.log.debug("insecure => #{@@insecure}".red);end
@@ -98,9 +100,10 @@ module Aspera
98
100
  public
99
101
 
100
102
  attr_reader :params
103
+ attr_reader :oauth
101
104
 
102
- # @param a_rest_params default call parameters and authentication (:auth) :
103
- # :type (:basic, :oauth2, :url)
105
+ # @param a_rest_params default call parameters (merged at call) and (+) authentication (:auth) :
106
+ # :type (:basic, :oauth2, :url, :none)
104
107
  # :username [:basic]
105
108
  # :password [:basic]
106
109
  # :url_creds [:url]
@@ -117,16 +120,6 @@ module Aspera
117
120
  # default is no auth
118
121
  @params[:auth]||={:type=>:none}
119
122
  @params[:not_auth_codes]||=['401']
120
- # translate old auth parameters, remove prefix, place in auth
121
- [:auth,:basic,:oauth].each do |p_sym|
122
- p_str=p_sym.to_s+'_'
123
- @params.keys.select{|k|k.to_s.start_with?(p_str)}.each do |k_sym|
124
- name=k_sym.to_s[p_str.length..-1]
125
- name='grant' if k_sym.eql?(:oauth_type)
126
- @params[:auth][name.to_sym]=@params[k_sym]
127
- @params.delete(k_sym)
128
- end
129
- end
130
123
  @oauth=Oauth.new(@params[:auth]) if @params[:auth][:type].eql?(:oauth2)
131
124
  Log.dump('REST params(2)',@params)
132
125
  end
@@ -153,6 +146,7 @@ module Aspera
153
146
  Log.log.debug("accessing #{call_data[:subpath]}".red.bold.bg_green)
154
147
  call_data[:headers]||={}
155
148
  call_data[:headers]['User-Agent'] ||= @@user_agent
149
+ # defaults from @params are overriden by call dataz
156
150
  call_data=@params.deep_merge(call_data)
157
151
  case call_data[:auth][:type]
158
152
  when :none
@@ -206,10 +200,13 @@ module Aspera
206
200
 
207
201
  Log.log.debug("call_data = #{call_data}")
208
202
  result={:http=>nil}
203
+ # start a block to be able to retry the actual HTTP request
209
204
  begin
210
205
  # we try the call, and will retry only if oauth, as we can, first with refresh, and then re-auth if refresh is bad
211
206
  oauth_tries ||= 2
207
+ tries_remain_redirect||=4
212
208
  Log.log.debug("send request")
209
+ # make http request (pipelined)
213
210
  http_session.request(req) do |response|
214
211
  result[:http] = response
215
212
  if call_data.has_key?(:save_to_file)
@@ -223,12 +220,12 @@ module Aspera
223
220
  target_file=call_data[:save_to_file]
224
221
  # override user's path to path in header
225
222
  if !response['Content-Disposition'].nil? and m=response['Content-Disposition'].match(/filename="([^"]+)"/)
226
- target_file=m[1]
223
+ target_file=File.join(File.dirname(target_file),m[1])
227
224
  end
228
225
  # download with temp filename
229
- target_file_tmp=target_file+'.http.partial'
226
+ target_file_tmp="#{target_file}#{@@download_partial_suffix}"
230
227
  Log.log.debug("saving to: #{target_file}")
231
- File.open(target_file_tmp, "wb") do |file|
228
+ File.open(target_file_tmp, 'wb') do |file|
232
229
  result[:http].read_body do |fragment|
233
230
  file.write(fragment)
234
231
  new_process=progress.progress+fragment.length
@@ -239,7 +236,7 @@ module Aspera
239
236
  # rename at the end
240
237
  File.rename(target_file_tmp, target_file)
241
238
  progress=nil
242
- end
239
+ end # save_to_file
243
240
  end
244
241
  # sometimes there is a ITF8 char (e.g. (c) )
245
242
  result[:http].body.force_encoding("UTF-8") if result[:http].body.is_a?(String)
@@ -267,10 +264,24 @@ module Aspera
267
264
  end
268
265
  Log.log.debug("using new token=#{call_data[:headers]['Authorization']}")
269
266
  retry unless (oauth_tries -= 1).zero?
270
- end # if
267
+ end # if oauth
268
+ # moved ?
269
+ if e.response.is_a?(Net::HTTPRedirection)
270
+ if tries_remain_redirect > 0
271
+ tries_remain_redirect-=1
272
+ Log.log.info("URL is moved: #{e.response['location']}")
273
+ raise e
274
+ # TODO: rebuild request with new location
275
+ #retry
276
+ else
277
+ raise "too many redirect"
278
+ end
279
+ else
280
+ raise e
281
+ end
271
282
  # raise exception if could not retry and not return error in result
272
283
  raise e unless call_data[:return_error]
273
- end
284
+ end # begin request
274
285
  Log.log.debug("result=#{result}")
275
286
  return result
276
287
 
@@ -0,0 +1,20 @@
1
+ module Aspera
2
+ # Manage secrets in CLI using secure way (encryption, wallet, etc...)
3
+ class Secrets
4
+ attr_accessor :default_secret,:all_secrets
5
+ def initialize()
6
+ @default_secret=nil
7
+ @all_secrets={}
8
+ end
9
+
10
+ def get_secret(id=nil,mandatory=true)
11
+ secret=@default_secret || @all_secrets[id]
12
+ raise "please provide secret for #{id}" if secret.nil? and mandatory
13
+ return secret
14
+ end
15
+
16
+ def get_secrets
17
+ return @all_secrets
18
+ end
19
+ end
20
+ end
data/lib/aspera/sync.rb CHANGED
@@ -1,48 +1,53 @@
1
+ require 'aspera/command_line_builder'
2
+
1
3
  module Aspera
2
4
  # builds command line arg for async
3
5
  class Sync
4
- private
5
6
  INSTANCE_PARAMS=
6
7
  {
7
- 'alt_logdir' => { :type => :opt_with_arg, :accepted_types=>String},
8
- 'watchd' => { :type => :opt_with_arg, :accepted_types=>String},
9
- 'apply_local_docroot' => { :type => :opt_without_arg},
10
- 'quiet' => { :type => :opt_without_arg},
8
+ 'alt_logdir' => { :cltype => :opt_with_arg, :accepted_types=>:string},
9
+ 'watchd' => { :cltype => :opt_with_arg, :accepted_types=>:string},
10
+ 'apply_local_docroot' => { :cltype => :opt_without_arg},
11
+ 'quiet' => { :cltype => :opt_without_arg},
11
12
  }
12
13
  SESSION_PARAMS=
13
14
  {
14
- 'name' => { :type => :opt_with_arg, :accepted_types=>String},
15
- 'local_dir' => { :type => :opt_with_arg, :accepted_types=>String},
16
- 'remote_dir' => { :type => :opt_with_arg, :accepted_types=>String},
17
- 'local_db_dir' => { :type => :opt_with_arg, :accepted_types=>String},
18
- 'remote_db_dir' => { :type => :opt_with_arg, :accepted_types=>String},
19
- 'host' => { :type => :opt_with_arg, :accepted_types=>String},
20
- 'user' => { :type => :opt_with_arg, :accepted_types=>String},
21
- 'private_key_path' => { :type => :opt_with_arg, :accepted_types=>String},
22
- 'direction' => { :type => :opt_with_arg, :accepted_types=>String},
23
- 'checksum' => { :type => :opt_with_arg, :accepted_types=>String},
24
- 'tcp_port' => { :type => :opt_with_arg, :accepted_types=>Integer},
25
- 'rate_policy' => { :type => :opt_with_arg, :accepted_types=>String},
26
- 'target_rate' => { :type => :opt_with_arg, :accepted_types=>String},
27
- 'cooloff' => { :type => :opt_with_arg, :accepted_types=>Integer},
28
- 'pending_max' => { :type => :opt_with_arg, :accepted_types=>Integer},
29
- 'scan_intensity' => { :type => :opt_with_arg, :accepted_types=>String},
30
- 'cipher' => { :type => :opt_with_arg, :accepted_types=>String},
31
- 'transfer_threads' => { :type => :opt_with_arg, :accepted_types=>Integer},
32
- 'preserve_time' => { :type => :opt_without_arg},
33
- 'preserve_access_time' => { :type => :opt_without_arg},
34
- 'preserve_modification_time' => { :type => :opt_without_arg},
35
- 'preserve_uid' => { :type => :opt_without_arg},
36
- 'preserve_gid' => { :type => :opt_without_arg},
37
- 'create_dir' => { :type => :opt_without_arg},
38
- 'reset' => { :type => :opt_without_arg},
15
+ 'name' => { :cltype => :opt_with_arg, :accepted_types=>:string},
16
+ 'local_dir' => { :cltype => :opt_with_arg, :accepted_types=>:string},
17
+ 'remote_dir' => { :cltype => :opt_with_arg, :accepted_types=>:string},
18
+ 'local_db_dir' => { :cltype => :opt_with_arg, :accepted_types=>:string},
19
+ 'remote_db_dir' => { :cltype => :opt_with_arg, :accepted_types=>:string},
20
+ 'host' => { :cltype => :opt_with_arg, :accepted_types=>:string},
21
+ 'user' => { :cltype => :opt_with_arg, :accepted_types=>:string},
22
+ 'private_key_path' => { :cltype => :opt_with_arg, :accepted_types=>:string},
23
+ 'direction' => { :cltype => :opt_with_arg, :accepted_types=>:string},
24
+ 'checksum' => { :cltype => :opt_with_arg, :accepted_types=>:string},
25
+ 'tcp_port' => { :cltype => :opt_with_arg, :accepted_types=>:int},
26
+ 'rate_policy' => { :cltype => :opt_with_arg, :accepted_types=>:string},
27
+ 'target_rate' => { :cltype => :opt_with_arg, :accepted_types=>:string},
28
+ 'cooloff' => { :cltype => :opt_with_arg, :accepted_types=>:int},
29
+ 'pending_max' => { :cltype => :opt_with_arg, :accepted_types=>:int},
30
+ 'scan_intensity' => { :cltype => :opt_with_arg, :accepted_types=>:string},
31
+ 'cipher' => { :cltype => :opt_with_arg, :accepted_types=>:string},
32
+ 'transfer_threads' => { :cltype => :opt_with_arg, :accepted_types=>:int},
33
+ 'preserve_time' => { :cltype => :opt_without_arg},
34
+ 'preserve_access_time' => { :cltype => :opt_without_arg},
35
+ 'preserve_modification_time' => { :cltype => :opt_without_arg},
36
+ 'preserve_uid' => { :cltype => :opt_without_arg},
37
+ 'preserve_gid' => { :cltype => :opt_without_arg},
38
+ 'create_dir' => { :cltype => :opt_without_arg},
39
+ 'reset' => { :cltype => :opt_without_arg},
39
40
  # note: only one env var, but multiple sessions... may be a problem
40
- 'remote_password' => { :type => :envvar, :variable=>'ASPERA_SCP_PASS'},
41
- 'cookie' => { :type => :envvar, :variable=>'ASPERA_SCP_COOKIE'},
42
- 'token' => { :type => :envvar, :variable=>'ASPERA_SCP_TOKEN'},
43
- 'license' => { :type => :envvar, :variable=>'ASPERA_SCP_LICENSE'},
41
+ 'remote_password' => { :cltype => :envvar, :clvarname=>'ASPERA_SCP_PASS'},
42
+ 'cookie' => { :cltype => :envvar, :clvarname=>'ASPERA_SCP_COOKIE'},
43
+ 'token' => { :cltype => :envvar, :clvarname=>'ASPERA_SCP_TOKEN'},
44
+ 'license' => { :cltype => :envvar, :clvarname=>'ASPERA_SCP_LICENSE'},
44
45
  }
45
- public
46
+
47
+ Aspera::CommandLineBuilder.normalize_description(INSTANCE_PARAMS)
48
+ Aspera::CommandLineBuilder.normalize_description(SESSION_PARAMS)
49
+
50
+ private_constant :INSTANCE_PARAMS,:SESSION_PARAMS
46
51
 
47
52
  def initialize(sync_params)
48
53
  @sync_params=sync_params
@@ -6,6 +6,11 @@ module Aspera
6
6
  # create a temp file name for a given folder
7
7
  # files can be deleted on process exit by calling cleanup
8
8
  class TempFileManager
9
+ SEC_IN_DAY=86400
10
+ # assume no transfer last longer than this
11
+ # (garbage collect file list which were not deleted after transfer)
12
+ FILE_LIST_AGE_MAX_SEC=5*SEC_IN_DAY
13
+ private_constant :SEC_IN_DAY,:FILE_LIST_AGE_MAX_SEC
9
14
  include Singleton
10
15
  def initialize
11
16
  @created_files=[]
@@ -33,5 +38,19 @@ module Aspera
33
38
  username = Etc.getlogin || Etc.getpwuid(Process.uid).name || 'unknown_user' rescue 'unknown_user'
34
39
  return new_file_path_in_folder(Etc.systmpdir,base_name+'_'+username+'_')
35
40
  end
41
+
42
+ def cleanup_expired(temp_folder)
43
+ # garbage collect undeleted files
44
+ Dir.entries(temp_folder).each do |name|
45
+ file_path=File.join(temp_folder,name)
46
+ age_sec=(Time.now - File.stat(file_path).mtime).to_i
47
+ # check age of file, delete too old
48
+ if File.file?(file_path) and age_sec > FILE_LIST_AGE_MAX_SEC
49
+ Log.log.debug("garbage collecting #{name}")
50
+ File.delete(file_path)
51
+ end
52
+ end
53
+
54
+ end
36
55
  end
37
56
  end