aspera-cli 4.2.1 → 4.5.0

Sign up to get free protection for your applications and to get access to all the features.
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