aspera-cli 4.0.0.pre3 → 4.2.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +695 -205
  3. data/bin/dascli +13 -0
  4. data/docs/README.erb.md +615 -157
  5. data/docs/test_env.conf +23 -5
  6. data/docs/transfer_spec.html +1 -1
  7. data/examples/aoc.rb +14 -3
  8. data/examples/faspex4.rb +78 -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 +46 -34
  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 +207 -182
  15. data/lib/aspera/cli/plugins/ats.rb +2 -2
  16. data/lib/aspera/cli/plugins/config.rb +173 -117
  17. data/lib/aspera/cli/plugins/console.rb +2 -2
  18. data/lib/aspera/cli/plugins/faspex.rb +51 -36
  19. data/lib/aspera/cli/plugins/faspex5.rb +82 -41
  20. data/lib/aspera/cli/plugins/node.rb +3 -3
  21. data/lib/aspera/cli/plugins/preview.rb +35 -25
  22. data/lib/aspera/cli/plugins/server.rb +23 -8
  23. data/lib/aspera/cli/transfer_agent.rb +7 -6
  24. data/lib/aspera/cli/version.rb +1 -1
  25. data/lib/aspera/cos_node.rb +33 -28
  26. data/lib/aspera/environment.rb +2 -2
  27. data/lib/aspera/fasp/connect.rb +28 -21
  28. data/lib/aspera/fasp/http_gw.rb +140 -28
  29. data/lib/aspera/fasp/installation.rb +101 -53
  30. data/lib/aspera/fasp/local.rb +88 -45
  31. data/lib/aspera/fasp/manager.rb +15 -0
  32. data/lib/aspera/fasp/node.rb +4 -4
  33. data/lib/aspera/fasp/parameters.rb +6 -18
  34. data/lib/aspera/fasp/resume_policy.rb +13 -12
  35. data/lib/aspera/log.rb +1 -1
  36. data/lib/aspera/node.rb +61 -1
  37. data/lib/aspera/oauth.rb +49 -46
  38. data/lib/aspera/persistency_folder.rb +9 -4
  39. data/lib/aspera/preview/file_types.rb +53 -21
  40. data/lib/aspera/preview/generator.rb +3 -3
  41. data/lib/aspera/rest.rb +29 -18
  42. data/lib/aspera/secrets.rb +20 -0
  43. data/lib/aspera/temp_file_manager.rb +19 -0
  44. data/lib/aspera/web_auth.rb +105 -0
  45. metadata +42 -22
@@ -45,6 +45,18 @@ module Aspera
45
45
  end
46
46
  end # notify_listeners
47
47
 
48
+ def notify_begin(id,size)
49
+ notify_listeners('emulated',{LISTENER_SESSION_ID_B=>id,'Type'=>'NOTIFICATION','PreTransferBytes'=>size})
50
+ end
51
+
52
+ def notify_progress(id,size)
53
+ notify_listeners('emulated',{LISTENER_SESSION_ID_B=>id,'Type'=>'STATS','Bytescont'=>size})
54
+ end
55
+
56
+ def notify_end(id)
57
+ notify_listeners('emulated',{LISTENER_SESSION_ID_B=>id,'Type'=>'DONE'})
58
+ end
59
+
48
60
  public
49
61
  LISTENER_SESSION_ID_B='ListenerSessionId'
50
62
  LISTENER_SESSION_ID_S='listener_session_id'
@@ -60,6 +72,9 @@ module Aspera
60
72
  # start_transfer(transfer_spec,options) : start and wait for completion
61
73
  # wait_for_transfers_completion : wait for termination of all transfers, @return list of : :success or error message
62
74
  # optional: shutdown
75
+
76
+ # This checks the validity of the value returned by wait_for_transfers_completion
77
+ # it must be a list of :success or exception
63
78
  def self.validate_status_list(statuses)
64
79
  raise "internal error: bad statuses type: #{statuses.class}" unless statuses.is_a?(Array)
65
80
  raise "internal error: bad statuses content: #{statuses}" unless statuses.select{|i|!i.eql?(:success) and !i.is_a?(StandardError)}.empty?
@@ -55,7 +55,7 @@ module Aspera
55
55
  trdata=node_api_.read("ops/transfers/#{@transfer_id}")[:data] || {"status"=>"unknown"} rescue {"status"=>"waiting(read error)"}
56
56
  case trdata['status']
57
57
  when 'completed'
58
- notify_listeners('emulated',{Manager::LISTENER_SESSION_ID_B=>@transfer_id,'Type'=>'DONE'})
58
+ notify_end(@transfer_id)
59
59
  break
60
60
  when 'waiting','partially_completed','unknown','waiting(read error)'
61
61
  if spinner.nil?
@@ -69,10 +69,10 @@ module Aspera
69
69
  #puts "running: sessions:#{trdata["sessions"].length}, #{trdata["sessions"].map{|i| i['bytes_transferred']}.join(',')}"
70
70
  if !started and trdata['precalc'].is_a?(Hash) and
71
71
  trdata['precalc']['status'].eql?('ready')
72
- notify_listeners('emulated',{Manager::LISTENER_SESSION_ID_B=>@transfer_id,'Type'=>'NOTIFICATION','PreTransferBytes'=>trdata['precalc']['bytes_expected']})
72
+ notify_begin(@transfer_id,trdata['precalc']['bytes_expected'])
73
73
  started=true
74
74
  else
75
- notify_listeners('emulated',{Manager::LISTENER_SESSION_ID_B=>@transfer_id,'Type'=>'STATS','Bytescont'=>trdata['bytes_transferred']})
75
+ notify_progress(@transfer_id,trdata['bytes_transferred'])
76
76
  end
77
77
  else
78
78
  Log.log.warn("trdata -> #{trdata}")
@@ -81,7 +81,7 @@ module Aspera
81
81
  sleep 1
82
82
  end
83
83
  #TODO get status of sessions
84
- return []
84
+ return []
85
85
  end
86
86
  end
87
87
  end
@@ -16,10 +16,6 @@ module Aspera
16
16
  # because of garbage collection takes any file there
17
17
  # this could be refined, as , for instance, on macos, temp folder is already user specific
18
18
  @@file_list_folder=TempFileManager.instance.new_file_path_global('asession_filelists')
19
- SEC_IN_DAY=86400
20
- # assume no transfer last longer than this
21
- # (garbage collect file list which were not deleted after transfer)
22
- FILE_LIST_AGE_MAX_SEC=5*SEC_IN_DAY
23
19
  PARAM_DEFINITION={
24
20
  # parameters with env vars
25
21
  'remote_password' => { :type => :envvar, :variable=>'ASPERA_SCP_PASS'},
@@ -63,7 +59,7 @@ module Aspera
63
59
  'exclude_older_than' => { :type => :opt_with_arg, :accepted_types=>Integer},
64
60
  'preserve_acls' => { :type => :opt_with_arg, :accepted_types=>String},
65
61
  'move_after_transfer' => { :type => :opt_with_arg, :accepted_types=>String},
66
- 'multi_session_threshold' => { :type => :opt_with_arg, :accepted_types=>String},
62
+ 'multi_session_threshold' => { :type => :opt_with_arg, :accepted_types=>Integer},
67
63
  # non standard parameters
68
64
  'EX_fasp_proxy_url' => { :type => :opt_with_arg, :option_switch=>'--proxy',:accepted_types=>String},
69
65
  'EX_http_proxy_url' => { :type => :opt_with_arg, :option_switch=>'-x',:accepted_types=>String},
@@ -82,11 +78,12 @@ module Aspera
82
78
  'lock_rate_policy' => { :type => :ignore, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
83
79
  'lock_min_rate' => { :type => :ignore, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
84
80
  'lock_target_rate' => { :type => :ignore, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
85
- #'authentication' => { :type => :ignore, :accepted_types=>String}, # = token
81
+ 'authentication' => { :type => :ignore, :accepted_types=>String}, # value = token
86
82
  'https_fallback_port' => { :type => :ignore, :accepted_types=>Integer}, # same as http fallback, option -t ?
87
83
  'content_protection' => { :type => :ignore, :accepted_types=>String},
88
84
  'cipher_allowed' => { :type => :ignore, :accepted_types=>String},
89
85
  'multi_session' => { :type => :ignore, :accepted_types=>Integer}, # managed
86
+ 'obfuscate_file_names' => { :type => :ignore, :accepted_types=>Aspera::CommandLineBuilder::BOOLEAN_CLASSES},
90
87
  # optional tags ( additional option to generate: {:space=>' ',:object_nl=>' ',:space_before=>'+',:array_nl=>'1'} )
91
88
  'tags' => { :type => :opt_with_arg, :option_switch=>'--tags64',:accepted_types=>Hash,:encode=>lambda{|tags|Base64.strict_encode64(JSON.generate(tags))}},
92
89
  # special processing @builder.process_param( called individually
@@ -100,7 +97,7 @@ module Aspera
100
97
  'wss_port' => { :type => :defer, :accepted_types=>Integer},
101
98
  }
102
99
 
103
- private_constant :SEC_IN_DAY,:FILE_LIST_AGE_MAX_SEC,:PARAM_DEFINITION
100
+ private_constant :PARAM_DEFINITION
104
101
 
105
102
  def initialize(job_spec,options)
106
103
  @job_spec=job_spec
@@ -207,18 +204,9 @@ module Aspera
207
204
  # temp file list files are created here
208
205
  def self.file_list_folder=(v)
209
206
  @@file_list_folder=v
210
- unless @@file_list_folder.nil?
207
+ if !@@file_list_folder.nil?
211
208
  FileUtils.mkdir_p(@@file_list_folder)
212
- # garbage collect undeleted files
213
- Dir.entries(@@file_list_folder).each do |name|
214
- file_path=File.join(@@file_list_folder,name)
215
- age_sec=(Time.now - File.stat(file_path).mtime).to_i
216
- # check age of file, delete too old
217
- if File.file?(file_path) and age_sec > FILE_LIST_AGE_MAX_SEC
218
- Log.log.debug("garbage collecting #{name}")
219
- File.delete(file_path)
220
- end
221
- end
209
+ TempFileManager.instance.cleanup_expired(@@file_list_folder)
222
210
  end
223
211
  end
224
212
 
@@ -14,17 +14,21 @@ module Aspera
14
14
  :sleep_max => 60
15
15
  }
16
16
 
17
- def initialize(params={})
17
+ # @param params see DEFAULTS
18
+ def initialize(params=nil)
18
19
  @parameters=DEFAULTS.clone
19
- return if params.nil?
20
- raise "expecting Hash (or nil), but have #{params.class}" unless params.is_a?(Hash)
21
- params.each do |k,v|
22
- if DEFAULTS.has_key?(k)
23
- @parameters[k]=v
24
- else
25
- raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map{|i|i.to_s}.join(",")}"
20
+ if !params.nil?
21
+ raise "expecting Hash (or nil), but have #{params.class}" unless params.is_a?(Hash)
22
+ params.each do |k,v|
23
+ if DEFAULTS.has_key?(k)
24
+ raise "#{k} must be Integer" unless v.is_a?(Integer)
25
+ @parameters[k]=v
26
+ else
27
+ raise "unknown resume parameter: #{k}, expect one of #{DEFAULTS.keys.map{|i|i.to_s}.join(",")}"
28
+ end
26
29
  end
27
30
  end
31
+ Log.log.debug("resume params=#{@parameters}")
28
32
  end
29
33
 
30
34
  # calls block a number of times (resumes) until success or limit reached
@@ -45,10 +49,7 @@ module Aspera
45
49
  # failure in ascp
46
50
  if e.retryable? then
47
51
  # exit if we exceed the max number of retry
48
- unless remaining_resumes > 0
49
- Log.log.error "Maximum number of retry reached"
50
- raise Fasp::Error,"max retry after: [#{status[:message]}]"
51
- end
52
+ raise Fasp::Error,'Maximum number of retry reached' if remaining_resumes <= 0
52
53
  else
53
54
  # give one chance only to non retryable errors
54
55
  unless remaining_resumes.eql?(@parameters[:iter_max])
data/lib/aspera/log.rb CHANGED
@@ -14,7 +14,7 @@ module Aspera
14
14
  attr_reader :logger
15
15
  attr_reader :logger_type
16
16
  # levels are :debug,:info,:warn,:error,fatal,:unknown
17
- def self.levels; Logger::Severity.constants.map{|c| c.downcase.to_sym};end
17
+ def self.levels; Logger::Severity.constants.sort{|a,b|Logger::Severity.const_get(a)<=>Logger::Severity.const_get(b)}.map{|c|c.downcase.to_sym};end
18
18
 
19
19
  # where logs are sent to
20
20
  def self.logtypes; [:stderr,:stdout,:syslog];end
data/lib/aspera/node.rb CHANGED
@@ -1,14 +1,74 @@
1
+ require 'aspera/rest'
2
+ require 'aspera/log'
1
3
  require 'zlib'
2
4
  require 'base64'
5
+
3
6
  module Aspera
4
7
  # Provides additional functions using node API.
5
8
  class Node < Rest
9
+ # permissions
10
+ ACCESS_LEVELS=['delete','list','mkdir','preview','read','rename','write']
11
+ MATCH_EXEC_PREFIX='exec:'
12
+
13
+ # for information only
6
14
  def self.decode_bearer_token(token)
7
15
  return JSON.parse(Zlib::Inflate.inflate(Base64.decode64(token)).partition('==SIGNATURE==').first)
8
16
  end
17
+
18
+ # for access keys: provide expression to match entry in folder
19
+ # if no prefix: regex
20
+ # if prefix: ruby code
21
+ # if filder is nil, then always match
22
+ def self.file_matcher(match_expression)
23
+ match_expression||="#{MATCH_EXEC_PREFIX}true"
24
+ if match_expression.start_with?(MATCH_EXEC_PREFIX)
25
+ return eval "lambda{|f|#{match_expression[MATCH_EXEC_PREFIX.length..-1]}}"
26
+ end
27
+ return lambda{|f|f['name'].match(/#{match_expression}/)}
28
+ end
29
+
9
30
  def initialize(rest_params)
10
31
  super(rest_params)
11
- # specifics here
32
+ end
33
+
34
+ # recursively crawl in a folder.
35
+ # subfolders a processed if the processing method returns true
36
+ # @param processor must provide a method to process each entry
37
+ # @param opt options
38
+ # - top_file_id file id to start at (default = access key root file id)
39
+ # - top_file_path path of top folder (default = /)
40
+ # - method processing method (default= process_entry)
41
+ def crawl(processor,opt={})
42
+ Log.log.debug("crawl1 #{opt}")
43
+ # not possible with bearer token
44
+ opt[:top_file_id] ||= read('access_keys/self')[:data]['root_file_id']
45
+ opt[:top_file_path] ||= '/'
46
+ opt[:method] ||= :process_entry
47
+ raise "processor must have #{opt[:method]}" unless processor.respond_to?(opt[:method])
48
+ Log.log.debug("crawl #{opt}")
49
+ #top_info=read("files/#{opt[:top_file_id]}")[:data]
50
+ folders_to_explore=[{id: opt[:top_file_id], relpath: opt[:top_file_path]}]
51
+ Log.dump(:folders_to_explore,folders_to_explore)
52
+ while !folders_to_explore.empty? do
53
+ current_item = folders_to_explore.shift
54
+ Log.log.debug("searching #{current_item[:relpath]}".bg_green)
55
+ # get folder content
56
+ folder_contents = begin
57
+ read("files/#{current_item[:id]}/files")[:data]
58
+ rescue => e
59
+ Log.log.warn("#{current_item[:relpath]}: #{e.class} #{e.message}")
60
+ []
61
+ end
62
+ Log.dump(:folder_contents,folder_contents)
63
+ folder_contents.each do |entry|
64
+ relative_path=File.join(current_item[:relpath],entry['name'])
65
+ Log.log.debug("looking #{relative_path}".bg_green)
66
+ # entry type is file, folder or link
67
+ if processor.send(opt[:method],entry,relative_path) and entry['type'].eql?('folder')
68
+ folders_to_explore.push({:id=>entry['id'],:relpath=>relative_path})
69
+ end
70
+ end
71
+ end
12
72
  end
13
73
  end
14
74
  end
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[:jwt_is_f5]
237
+ payload[:jti] = SecureRandom.uuid
238
+ payload[:iat] = seconds_since_epoch
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=>:none}
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