aspera-cli 4.0.0.pre3 → 4.2.1

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 (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