aspera-cli 4.2.1 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1580 -946
  3. data/bin/ascli +1 -1
  4. data/bin/asession +3 -5
  5. data/docs/Makefile +8 -11
  6. data/docs/README.erb.md +1521 -829
  7. data/docs/doc_tools.rb +58 -0
  8. data/docs/test_env.conf +3 -1
  9. data/examples/faspex4.rb +28 -19
  10. data/examples/transfer.rb +2 -2
  11. data/lib/aspera/aoc.rb +157 -134
  12. data/lib/aspera/cli/listener/progress_multi.rb +5 -5
  13. data/lib/aspera/cli/main.rb +106 -48
  14. data/lib/aspera/cli/manager.rb +19 -20
  15. data/lib/aspera/cli/plugin.rb +22 -7
  16. data/lib/aspera/cli/plugins/aoc.rb +260 -208
  17. data/lib/aspera/cli/plugins/ats.rb +11 -10
  18. data/lib/aspera/cli/plugins/bss.rb +2 -2
  19. data/lib/aspera/cli/plugins/config.rb +360 -189
  20. data/lib/aspera/cli/plugins/faspex.rb +119 -56
  21. data/lib/aspera/cli/plugins/faspex5.rb +32 -17
  22. data/lib/aspera/cli/plugins/node.rb +72 -31
  23. data/lib/aspera/cli/plugins/orchestrator.rb +5 -3
  24. data/lib/aspera/cli/plugins/preview.rb +94 -68
  25. data/lib/aspera/cli/plugins/server.rb +16 -5
  26. data/lib/aspera/cli/plugins/shares.rb +17 -0
  27. data/lib/aspera/cli/transfer_agent.rb +64 -82
  28. data/lib/aspera/cli/version.rb +1 -1
  29. data/lib/aspera/command_line_builder.rb +48 -31
  30. data/lib/aspera/cos_node.rb +4 -3
  31. data/lib/aspera/environment.rb +4 -4
  32. data/lib/aspera/fasp/{manager.rb → agent_base.rb} +7 -6
  33. data/lib/aspera/fasp/{connect.rb → agent_connect.rb} +46 -39
  34. data/lib/aspera/fasp/{local.rb → agent_direct.rb} +42 -38
  35. data/lib/aspera/fasp/{http_gw.rb → agent_httpgw.rb} +50 -29
  36. data/lib/aspera/fasp/{node.rb → agent_node.rb} +43 -4
  37. data/lib/aspera/fasp/agent_trsdk.rb +106 -0
  38. data/lib/aspera/fasp/default.rb +17 -0
  39. data/lib/aspera/fasp/installation.rb +64 -48
  40. data/lib/aspera/fasp/parameters.rb +78 -91
  41. data/lib/aspera/fasp/parameters.yaml +531 -0
  42. data/lib/aspera/fasp/uri.rb +1 -1
  43. data/lib/aspera/faspex_gw.rb +12 -11
  44. data/lib/aspera/id_generator.rb +22 -0
  45. data/lib/aspera/keychain/encrypted_hash.rb +120 -0
  46. data/lib/aspera/keychain/macos_security.rb +94 -0
  47. data/lib/aspera/log.rb +45 -32
  48. data/lib/aspera/node.rb +9 -4
  49. data/lib/aspera/oauth.rb +116 -100
  50. data/lib/aspera/persistency_action_once.rb +11 -7
  51. data/lib/aspera/persistency_folder.rb +6 -26
  52. data/lib/aspera/rest.rb +66 -50
  53. data/lib/aspera/sync.rb +40 -35
  54. data/lib/aspera/timer_limiter.rb +22 -0
  55. metadata +86 -29
  56. data/docs/transfer_spec.html +0 -99
  57. data/lib/aspera/api_detector.rb +0 -60
  58. data/lib/aspera/fasp/aoc.rb +0 -24
  59. data/lib/aspera/secrets.rb +0 -20
data/lib/aspera/oauth.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'aspera/open_application'
2
2
  require 'aspera/web_auth'
3
+ require 'aspera/id_generator'
3
4
  require 'base64'
4
5
  require 'date'
5
6
  require 'socket'
@@ -9,8 +10,14 @@ module Aspera
9
10
  # Implement OAuth 2 for the REST client and generate a bearer token
10
11
  # call get_authorization() to get a token.
11
12
  # bearer tokens are kept in memory and also in a file cache for later re-use
12
- # if a token is expired (api returns 4xx), call again get_authorization({:refresh=>true})
13
+ # if a token is expired (api returns 4xx), call again get_authorization({refresh: true})
13
14
  class Oauth
15
+ # used for code exchange
16
+ DEFAULT_PATH_AUTHORIZE='authorize'
17
+ # to generate token
18
+ DEFAULT_PATH_TOKEN='token'
19
+ # field with token in result
20
+ DEFAULT_TOKEN_FIELD='access_token'
14
21
  private
15
22
  # remove 5 minutes to account for time offset (TODO: configurable?)
16
23
  JWT_NOTBEFORE_OFFSET_SEC=300
@@ -20,7 +27,9 @@ module Aspera
20
27
  TOKEN_CACHE_EXPIRY_SEC=1800
21
28
  # a prefix for persistency of tokens (garbage collect)
22
29
  PERSIST_CATEGORY_TOKEN='token'
23
- private_constant :JWT_NOTBEFORE_OFFSET_SEC,:JWT_EXPIRY_OFFSET_SEC,:PERSIST_CATEGORY_TOKEN,:TOKEN_CACHE_EXPIRY_SEC
30
+ ONE_HOUR_AS_DAY_FRACTION=Rational(1,24)
31
+
32
+ private_constant :JWT_NOTBEFORE_OFFSET_SEC,:JWT_EXPIRY_OFFSET_SEC,:PERSIST_CATEGORY_TOKEN,:TOKEN_CACHE_EXPIRY_SEC,:ONE_HOUR_AS_DAY_FRACTION
24
33
  class << self
25
34
  # OAuth methods supported
26
35
  def auth_types
@@ -45,8 +54,25 @@ module Aspera
45
54
  def flush_tokens
46
55
  persist_mgr.garbage_collect(PERSIST_CATEGORY_TOKEN,nil)
47
56
  end
57
+
58
+ def register_decoder(method)
59
+ @decoders||=[]
60
+ @decoders.push(method)
61
+ end
62
+
63
+ def decode_token(token)
64
+ Log.log.debug(">>>> #{token} : #{@decoders.length}")
65
+ @decoders.each do |decoder|
66
+ result=decoder.call(token) rescue nil
67
+ return result unless result.nil?
68
+ end
69
+ return nil
70
+ end
48
71
  end
49
72
 
73
+ # seems to be quite standard token encoding (RFC?)
74
+ self.register_decoder lambda { |token| parts=token.split('.'); raise "not aoc token" unless parts.length.eql?(3); JSON.parse(Base64.decode64(parts[1]))}
75
+
50
76
  # for supported parameters, look in the code for @params
51
77
  # parameters are provided all with oauth_ prefix :
52
78
  # :base_url
@@ -56,8 +82,8 @@ module Aspera
56
82
  # :jwt_audience
57
83
  # :jwt_private_key_obj
58
84
  # :jwt_subject
59
- # :path_authorize (default: 'authorize')
60
- # :path_token (default: 'token')
85
+ # :path_authorize (default: DEFAULT_PATH_AUTHORIZE)
86
+ # :path_token (default: DEFAULT_PATH_TOKEN)
61
87
  # :scope (optional)
62
88
  # :grant (one of returned by self.auth_types)
63
89
  # :url_token
@@ -65,21 +91,21 @@ module Aspera
65
91
  # :user_pass
66
92
  # :token_type
67
93
  def initialize(auth_params)
68
- Log.log.debug "auth=#{auth_params}"
94
+ Log.log.debug("auth=#{auth_params}")
69
95
  @params=auth_params.clone
70
96
  # default values
71
97
  # name of field to take as token from result of call to /token
72
- @params[:token_field]||='access_token'
98
+ @params[:token_field]||=DEFAULT_TOKEN_FIELD
73
99
  # default endpoint for /token
74
- @params[:path_token]||='token'
100
+ @params[:path_token]||=DEFAULT_PATH_TOKEN
75
101
  # default endpoint for /authorize
76
- @params[:path_authorize]||='authorize'
77
- rest_params={:base_url => @params[:base_url]}
102
+ @params[:path_authorize]||=DEFAULT_PATH_AUTHORIZE
103
+ rest_params={base_url: @params[:base_url]}
78
104
  if @params.has_key?(:client_id)
79
- rest_params.merge!({:auth => {
80
- :type => :basic,
81
- :username => @params[:client_id],
82
- :password => @params[:client_secret]
105
+ rest_params.merge!({auth: {
106
+ type: :basic,
107
+ username: @params[:client_id],
108
+ password: @params[:client_secret]
83
109
  }})
84
110
  end
85
111
  @token_auth_api=Rest.new(rest_params)
@@ -107,28 +133,20 @@ module Aspera
107
133
  return code
108
134
  end
109
135
 
110
- def create_token_advanced(rest_params)
136
+ def create_token(rest_params)
111
137
  return @token_auth_api.call({
112
- :operation => 'POST',
113
- :subpath => @params[:path_token],
114
- :headers => {'Accept'=>'application/json'}}.merge(rest_params))
115
- end
116
-
117
- # shortcut for create_token_advanced
118
- def create_token_www_body(creation_params)
119
- return create_token_advanced({:www_body_params=>creation_params})
138
+ operation: 'POST',
139
+ subpath: @params[:path_token],
140
+ headers: {'Accept'=>'application/json'}}.merge(rest_params))
120
141
  end
121
142
 
122
- # @return Array list of unique identifiers of token
123
- def token_cache_ids(api_scope)
143
+ # @return unique identifier of token
144
+ def token_cache_id(api_scope)
124
145
  oauth_uri=URI.parse(@params[:base_url])
125
- parts=[PERSIST_CATEGORY_TOKEN,oauth_uri.host.downcase.gsub(/[^a-z]+/,'_'),oauth_uri.path.downcase.gsub(/[^a-z]+/,'_'),@params[:grant]]
126
- parts.push(api_scope) unless api_scope.nil?
127
- parts.push(@params[:jwt_subject]) if @params.has_key?(:jwt_subject)
128
- parts.push(@params[:user_name]) if @params.has_key?(:user_name)
129
- parts.push(@params[:url_token]) if @params.has_key?(:url_token)
130
- parts.push(@params[:api_key]) if @params.has_key?(:api_key)
131
- return parts
146
+ parts=[PERSIST_CATEGORY_TOKEN,api_scope,oauth_uri.host,oauth_uri.path]
147
+ # add some of the parameters that uniquely define the token
148
+ [:grant,:jwt_subject,:user_name,:url_token,:api_key].inject(parts){|p,i|p.push(@params[i])}
149
+ return IdGenerator.from_list(parts)
132
150
  end
133
151
 
134
152
  public
@@ -148,22 +166,23 @@ module Aspera
148
166
  use_refresh_token=options[:refresh]
149
167
 
150
168
  # generate token identifier to use with cache
151
- token_ids=token_cache_ids(api_scope)
169
+ token_id=token_cache_id(api_scope)
152
170
 
153
171
  # get token_data from cache (or nil), token_data is what is returned by /token
154
- token_data=self.class.persist_mgr.get(token_ids)
172
+ token_data=self.class.persist_mgr.get(token_id)
155
173
  token_data=JSON.parse(token_data) unless token_data.nil?
156
-
157
174
  # Optional optimization: check if node token is expired, then force refresh
158
175
  # in case the transfer agent cannot refresh himself
159
176
  # else, anyway, faspmanager is equipped with refresh code
160
177
  if !token_data.nil?
161
- decoded_node_token = Node.decode_bearer_token(token_data['access_token']) rescue nil
178
+ # TODO: use @params[:token_field] ?
179
+ decoded_node_token = self.class.decode_token(token_data['access_token'])
180
+ Log.dump('decoded_node_token',decoded_node_token) unless decoded_node_token.nil?
162
181
  if decoded_node_token.is_a?(Hash) and decoded_node_token['expires_at'].is_a?(String)
163
- Log.dump('decoded_node_token',decoded_node_token)
164
182
  expires_at=DateTime.parse(decoded_node_token['expires_at'])
165
- one_hour_as_day_fraction=Rational(1,24)
166
- use_refresh_token=true if DateTime.now > (expires_at-one_hour_as_day_fraction)
183
+ # Time.at(decoded_node_token['exp'])
184
+ # does it seem expired, with one hour of security
185
+ use_refresh_token=true if DateTime.now > (expires_at-ONE_HOUR_AS_DAY_FRACTION)
167
186
  end
168
187
  end
169
188
 
@@ -174,7 +193,7 @@ module Aspera
174
193
  refresh_token=token_data['refresh_token']
175
194
  end
176
195
  # delete caches
177
- self.class.persist_mgr.delete(token_ids)
196
+ self.class.persist_mgr.delete(token_id)
178
197
  token_data=nil
179
198
  # lets try the existing refresh token
180
199
  if !refresh_token.nil?
@@ -182,14 +201,14 @@ module Aspera
182
201
  # try to refresh
183
202
  # note: admin token has no refresh, and lives by default 1800secs
184
203
  # Note: scope is mandatory in Files, and we can either provide basic auth, or client_Secret in data
185
- resp=create_token_www_body(p_client_id_and_scope.merge({
186
- :grant_type =>'refresh_token',
187
- :refresh_token=>refresh_token}))
204
+ resp=create_token(www_body_params: p_client_id_and_scope.merge({
205
+ grant_type: 'refresh_token',
206
+ refresh_token: refresh_token}))
188
207
  if resp[:http].code.start_with?('2') then
189
- # save only if success ?
208
+ # save only if success
190
209
  json_data=resp[:http].body
191
210
  token_data=JSON.parse(json_data)
192
- self.class.persist_mgr.put(token_ids,json_data)
211
+ self.class.persist_mgr.put(token_id,json_data)
193
212
  else
194
213
  Log.log.debug("refresh failed: #{resp[:http].body}".bg_red)
195
214
  end
@@ -204,19 +223,19 @@ module Aspera
204
223
  # AoC Web based Auth
205
224
  check_code=SecureRandom.uuid
206
225
  auth_params=p_client_id_and_scope.merge({
207
- :response_type => 'code',
208
- :redirect_uri => @params[:redirect_uri],
209
- :state => check_code
226
+ response_type: 'code',
227
+ redirect_uri: @params[:redirect_uri],
228
+ state: check_code
210
229
  })
211
230
  auth_params[:client_secret]=@params[:client_secret] if @params.has_key?(:client_secret)
212
231
  login_page_url=Rest.build_uri("#{@params[:base_url]}/#{@params[:path_authorize]}",auth_params)
213
232
  # here, we need a human to authorize on a web page
214
233
  code=goto_page_and_get_code(login_page_url,check_code)
215
234
  # exchange code for token
216
- resp=create_token_www_body(p_client_id_and_scope.merge({
217
- :grant_type => 'authorization_code',
218
- :code => code,
219
- :redirect_uri => @params[:redirect_uri]
235
+ resp=create_token(www_body_params: p_client_id_and_scope.merge({
236
+ grant_type: 'authorization_code',
237
+ code: code,
238
+ redirect_uri: @params[:redirect_uri]
220
239
  }))
221
240
  when :jwt
222
241
  # https://tools.ietf.org/html/rfc7519
@@ -226,84 +245,81 @@ module Aspera
226
245
  Log.log.info("seconds=#{seconds_since_epoch}")
227
246
 
228
247
  payload = {
229
- :iss => @params[:client_id], # issuer
230
- :sub => @params[:jwt_subject], # subject
231
- :aud => @params[:jwt_audience], # audience
232
- :nbf => seconds_since_epoch-JWT_NOTBEFORE_OFFSET_SEC, # not before
233
- :exp => seconds_since_epoch+JWT_EXPIRY_OFFSET_SEC # expiration
248
+ iss: @params[:client_id], # issuer
249
+ sub: @params[:jwt_subject], # subject
250
+ aud: @params[:jwt_audience], # audience
251
+ nbf: seconds_since_epoch-JWT_NOTBEFORE_OFFSET_SEC, # not before
252
+ exp: seconds_since_epoch+JWT_EXPIRY_OFFSET_SEC # expiration
234
253
  }
235
254
  # 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"
255
+ if @params.has_key?(:f5_username)
256
+ payload[:jti] = SecureRandom.uuid # JWT id
257
+ payload[:iat] = seconds_since_epoch # issued at
258
+ payload.delete(:nbf) # not used in f5
259
+ p_scope[:redirect_uri]="https://127.0.0.1:5000/token" # used ?
241
260
  p_scope[:state]=SecureRandom.uuid
242
261
  p_scope[:client_id]=@params[:client_id]
243
- @token_auth_api.params[:auth]={:type=>:none}
262
+ @token_auth_api.params[:auth]={type: :basic,username: @params[:f5_username], password: @params[:f5_password]}
244
263
  end
245
264
 
246
265
  # non standard, only for global ids
247
266
  payload.merge!(@params[:jwt_add]) if @params.has_key?(:jwt_add)
267
+ Log.log.debug("JWT payload=[#{payload}]")
248
268
 
249
269
  rsa_private=@params[:jwt_private_key_obj] # type: OpenSSL::PKey::RSA
250
-
251
270
  Log.log.debug("private=[#{rsa_private}]")
252
271
 
253
- Log.log.debug("JWT payload=[#{payload}]")
254
- assertion = JWT.encode(payload, rsa_private, 'RS256',@params[:jwt_headers]||{})
255
-
272
+ assertion = JWT.encode(payload, rsa_private, 'RS256', @params[:jwt_headers]||{})
256
273
  Log.log.debug("assertion=[#{assertion}]")
257
274
 
258
- resp=create_token_www_body(p_scope.merge({
259
- :grant_type => 'urn:ietf:params:oauth:grant-type:jwt-bearer',
260
- :assertion => assertion
275
+ resp=create_token(www_body_params: p_scope.merge({
276
+ grant_type: 'urn:ietf:params:oauth:grant-type:jwt-bearer',
277
+ assertion: assertion
261
278
  }))
262
279
  when :url_token
263
280
  # AoC Public Link
264
- params={:url_token=>@params[:url_token]}
281
+ params={url_token: @params[:url_token]}
265
282
  params[:password]=@params[:password] if @params.has_key?(:password)
266
- resp=create_token_advanced({
267
- :json_params => params,
268
- :url_params => p_scope.merge({
269
- :grant_type => 'url_token'
270
- })})
283
+ resp=create_token({
284
+ json_params: params,
285
+ url_params: p_scope.merge({grant_type: 'url_token'})
286
+ })
271
287
  when :ibm_apikey
272
288
  # ATS
273
- resp=create_token_www_body({
274
- 'grant_type' => 'urn:ibm:params:oauth:grant-type:apikey',
275
- 'response_type' => 'cloud_iam',
276
- 'apikey' => @params[:api_key]
289
+ resp=create_token(www_body_params: {
290
+ grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
291
+ response_type: 'cloud_iam',
292
+ apikey: @params[:api_key]
277
293
  })
278
294
  when :delegated_refresh
279
295
  # COS
280
- resp=create_token_www_body({
281
- 'grant_type' => 'urn:ibm:params:oauth:grant-type:apikey',
282
- 'response_type' => 'delegated_refresh_token',
283
- 'apikey' => @params[:api_key],
284
- 'receiver_client_ids' => 'aspera_ats'
296
+ resp=create_token(www_body_params: {
297
+ grant_type: 'urn:ibm:params:oauth:grant-type:apikey',
298
+ response_type: 'delegated_refresh_token',
299
+ apikey: @params[:api_key],
300
+ receiver_client_ids: 'aspera_ats'
285
301
  })
286
302
  when :header_userpass
287
303
  # used in Faspex apiv4 and shares2
288
- resp=create_token_advanced({
289
- :auth => {
290
- :type => :basic,
291
- :username => @params[:user_name],
292
- :password => @params[:user_pass]},
293
- :json_params => p_client_id_and_scope.merge({:grant_type => 'password'}), #:www_body_params also works
294
- })
304
+ resp=create_token(
305
+ json_params: p_client_id_and_scope.merge({grant_type: 'password'}), #:www_body_params also works
306
+ auth: {
307
+ type: :basic,
308
+ username: @params[:user_name],
309
+ password: @params[:user_pass]}
310
+ )
295
311
  when :body_userpass
296
312
  # legacy, not used
297
- resp=create_token_www_body(p_client_id_and_scope.merge({
298
- :grant_type => 'password',
299
- :username => @params[:user_name],
300
- :password => @params[:user_pass]
313
+ resp=create_token(www_body_params: p_client_id_and_scope.merge({
314
+ grant_type: 'password',
315
+ username: @params[:user_name],
316
+ password: @params[:user_pass]
301
317
  }))
302
318
  when :body_data
303
319
  # used in Faspex apiv5
304
- resp=create_token_advanced({
305
- :auth => {:type => :none},
306
- :json_params => @params[:userpass_body],
320
+ resp=create_token({
321
+ auth: {type: :none},
322
+ json_params: @params[:userpass_body],
307
323
  })
308
324
  else
309
325
  raise "auth grant type unknown: #{@params[:grant]}"
@@ -311,9 +327,9 @@ module Aspera
311
327
  # TODO: test return code ?
312
328
  json_data=resp[:http].body
313
329
  token_data=JSON.parse(json_data)
314
- self.class.persist_mgr.put(token_ids,json_data)
330
+ self.class.persist_mgr.put(token_id,json_data)
315
331
  end # if ! in_cache
316
-
332
+ raise "API error: No such field in answer: #{@params[:token_field]}" unless token_data.has_key?(@params[:token_field])
317
333
  # ok we shall have a token here
318
334
  return 'Bearer '+token_data[@params[:token_field]]
319
335
  end
@@ -6,7 +6,7 @@ module Aspera
6
6
  class PersistencyActionOnce
7
7
  # @param :manager Mandatory Database
8
8
  # @param :data Mandatory object to persist, must be same object from begin to end (assume array by default)
9
- # @param :ids Mandatory identifiers
9
+ # @param :id Mandatory identifiers
10
10
  # @param :delete Optional delete persistency condition
11
11
  # @param :parse Optional parse method (default to JSON)
12
12
  # @param :format Optional dump method (default to JSON)
@@ -16,27 +16,31 @@ module Aspera
16
16
  raise "options shall be Hash" unless options.is_a?(Hash)
17
17
  raise "mandatory :manager" if options[:manager].nil?
18
18
  raise "mandatory :data" if options[:data].nil?
19
- raise "mandatory :ids (Array)" unless options[:ids].is_a?(Array)
20
- raise "mandatory 1 element in :ids" unless options[:ids].length >= 1
19
+ raise "mandatory :id (String)" unless options[:id].is_a?(String)
20
+ raise "mandatory 1 element in :id" unless options[:id].length >= 1
21
21
  @manager=options[:manager]
22
22
  @persisted_object=options[:data]
23
- @object_ids=options[:ids]
23
+ @object_id=options[:id]
24
24
  # by default , at save time, file is deleted if data is nil
25
25
  @delete_condition=options[:delete] || lambda{|d|d.empty?}
26
26
  @persist_format=options[:format] || lambda {|h| JSON.generate(h)}
27
27
  persist_parse=options[:parse] || lambda {|t| JSON.parse(t)}
28
28
  persist_merge=options[:merge] || lambda {|current,file| current.concat(file).uniq rescue current}
29
- value=@manager.get(@object_ids)
29
+ value=@manager.get(@object_id)
30
30
  persist_merge.call(@persisted_object,persist_parse.call(value)) unless value.nil?
31
31
  end
32
32
 
33
33
  def save
34
34
  if @delete_condition.call(@persisted_object)
35
- @manager.delete(@object_ids)
35
+ @manager.delete(@object_id)
36
36
  else
37
- @manager.put(@object_ids,@persist_format.call(@persisted_object))
37
+ @manager.put(@object_id,@persist_format.call(@persisted_object))
38
38
  end
39
39
  end
40
40
 
41
+ def data
42
+ return @persisted_object
43
+ end
44
+
41
45
  end
42
46
  end
@@ -6,11 +6,8 @@ require 'aspera/log'
6
6
  module Aspera
7
7
  # Persist data on file system
8
8
  class PersistencyFolder
9
- WINDOWS_PROTECTED_CHAR=%r{[/:"<>\\\*\?]}
10
- PROTECTED_CHAR_REPLACE='_'
11
- ID_SEPARATOR='_'
12
9
  FILE_SUFFIX='.txt'
13
- private_constant :PROTECTED_CHAR_REPLACE,:FILE_SUFFIX,:WINDOWS_PROTECTED_CHAR,:ID_SEPARATOR
10
+ private_constant :FILE_SUFFIX
14
11
  def initialize(folder)
15
12
  @cache={}
16
13
  set_folder(folder)
@@ -23,7 +20,6 @@ module Aspera
23
20
 
24
21
  # @return String or nil string on existing persist, else nil
25
22
  def get(object_id)
26
- object_id=marshalled_id(object_id)
27
23
  Log.log.debug("persistency get: #{object_id}")
28
24
  if @cache.has_key?(object_id)
29
25
  Log.log.debug("got from memory cache")
@@ -39,18 +35,16 @@ module Aspera
39
35
  end
40
36
 
41
37
  def put(object_id,value)
42
- raise "only String supported" unless value.is_a?(String)
43
- object_id=marshalled_id(object_id)
38
+ raise "value: only String supported" unless value.is_a?(String)
44
39
  persist_filepath=id_to_filepath(object_id)
45
- Log.log.debug("saving: #{persist_filepath}")
40
+ Log.log.debug("persistency saving: #{persist_filepath}")
46
41
  File.write(persist_filepath,value)
47
42
  @cache[object_id]=value
48
43
  end
49
44
 
50
45
  def delete(object_id)
51
- object_id=marshalled_id(object_id)
52
46
  persist_filepath=id_to_filepath(object_id)
53
- Log.log.debug("empty data, deleting: #{persist_filepath}")
47
+ Log.log.debug("persistency deleting: #{persist_filepath}")
54
48
  File.delete(persist_filepath) if File.exist?(persist_filepath)
55
49
  @cache.delete(object_id)
56
50
  end
@@ -63,7 +57,7 @@ module Aspera
63
57
  end
64
58
  garbage_files.each do |filepath|
65
59
  File.delete(filepath)
66
- Log.log.debug("Deleted expired: #{filepath}")
60
+ Log.log.debug("persistency deleted expired: #{filepath}")
67
61
  end
68
62
  return garbage_files
69
63
  end
@@ -72,25 +66,11 @@ module Aspera
72
66
 
73
67
  # @param object_id String or Array
74
68
  def id_to_filepath(object_id)
69
+ raise "object_id: only String supported" unless object_id.is_a?(String)
75
70
  FileUtils.mkdir_p(@folder)
76
71
  return File.join(@folder,"#{object_id}#{FILE_SUFFIX}")
77
72
  #.gsub(/[^a-z]+/,FILE_FIELD_SEPARATOR)
78
73
  end
79
74
 
80
- def marshalled_id(object_id)
81
- if object_id.is_a?(Array)
82
- # special case, url in second position: TODO: check any position
83
- if object_id[1].is_a?(String) and object_id[1] =~ URI::ABS_URI
84
- object_id=object_id.clone
85
- object_id[1]=URI.parse(object_id[1]).host
86
- end
87
- object_id=object_id.join(ID_SEPARATOR)
88
- end
89
- raise "id must be a String" unless object_id.is_a?(String)
90
- return object_id.
91
- gsub(WINDOWS_PROTECTED_CHAR,PROTECTED_CHAR_REPLACE). # remove windows forbidden chars
92
- gsub('.',PROTECTED_CHAR_REPLACE). # keep dot for extension only (nicer)
93
- downcase
94
- end
95
75
  end # PersistencyFolder
96
76
  end # Aspera