sdb_dal 0.0.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.
- data/lib/sdb_dal/and_condition.rb +10 -0
- data/lib/sdb_dal/attribute_range.rb +57 -0
- data/lib/sdb_dal/domain_attribute_description.rb +83 -0
- data/lib/sdb_dal/domain_object.rb +642 -0
- data/lib/sdb_dal/domain_object_cache_item.rb +14 -0
- data/lib/sdb_dal/equals_condition.rb +18 -0
- data/lib/sdb_dal/geo.rb +188 -0
- data/lib/sdb_dal/index_description.rb +29 -0
- data/lib/sdb_dal/is_null_transform.rb +16 -0
- data/lib/sdb_dal/lazy_loading_text.rb +27 -0
- data/lib/sdb_dal/memory_repository.rb +168 -0
- data/lib/sdb_dal/memory_storage.rb +29 -0
- data/lib/sdb_dal/or_condition.rb +53 -0
- data/lib/sdb_dal/query_string_auth_generator.rb +166 -0
- data/lib/sdb_dal/reference.rb +28 -0
- data/lib/sdb_dal/repository.rb +366 -0
- data/lib/sdb_dal/s3.rb +594 -0
- data/lib/sdb_dal/sdb_formatter.rb +119 -0
- data/lib/sdb_dal/starts_with_condition.rb +21 -0
- data/lib/sdb_dal/storage.rb +188 -0
- data/lib/sdb_dal/tag_cloud.rb +56 -0
- data/lib/sdb_dal/tracker_description.rb +26 -0
- data/lib/sdb_dal.rb +8 -0
- metadata +75 -0
data/lib/sdb_dal/s3.rb
ADDED
@@ -0,0 +1,594 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
# This software code is made available "AS IS" without warranties of any
|
4
|
+
# kind. You may copy, display, modify and redistribute the software
|
5
|
+
# code either by itself or as incorporated into your code; provided that
|
6
|
+
# you do not remove any proprietary notices. Your use of this software
|
7
|
+
# code is at your own risk and you waive any claim against Amazon
|
8
|
+
# Digital Services, Inc. or its affiliates with respect to your use of
|
9
|
+
# this software code. (c) 2006 Amazon Digital Services, Inc. or its
|
10
|
+
# affiliates.
|
11
|
+
|
12
|
+
# Note that this is the Amazon S3 sample modified for DevPay. You can diff
|
13
|
+
# this file with the Amazon S3 Ruby library to see the DevPay modifications.
|
14
|
+
|
15
|
+
require 'base64'
|
16
|
+
require 'cgi'
|
17
|
+
require 'openssl'
|
18
|
+
require 'digest/sha1'
|
19
|
+
require 'net/https'
|
20
|
+
require 'rexml/document'
|
21
|
+
require 'time'
|
22
|
+
|
23
|
+
# this wasn't added until v 1.8.3
|
24
|
+
if (RUBY_VERSION < '1.8.3')
|
25
|
+
class Net::HTTP::Delete < Net::HTTPRequest
|
26
|
+
METHOD = 'DELETE'
|
27
|
+
REQUEST_HAS_BODY = false
|
28
|
+
RESPONSE_HAS_BODY = true
|
29
|
+
end
|
30
|
+
end
|
31
|
+
module SdbDal
|
32
|
+
|
33
|
+
# this module has two big classes: AWSAuthConnection and
|
34
|
+
# QueryStringAuthGenerator. both use identical apis, but the first actually
|
35
|
+
# performs the operation, while the second simply outputs urls with the
|
36
|
+
# appropriate authentication query string parameters, which could be used
|
37
|
+
# in another tool (such as your web browser for GETs).
|
38
|
+
module S3
|
39
|
+
DEFAULT_HOST = 's3.amazonaws.com'
|
40
|
+
PORTS_BY_SECURITY = { true => 443, false => 80 }
|
41
|
+
METADATA_PREFIX = 'x-amz-meta-'
|
42
|
+
AMAZON_HEADER_PREFIX = 'x-amz-'
|
43
|
+
AMAZON_TOKEN_HEADER_PREFIX = "x-amz-security-token"
|
44
|
+
|
45
|
+
# builds the canonical string for signing.
|
46
|
+
def S3.canonical_string(method, bucket="", path="", path_args={}, headers={}, expires=nil)
|
47
|
+
interesting_headers = {}
|
48
|
+
headers.each do |key, value|
|
49
|
+
lk = key.downcase
|
50
|
+
if (lk == 'content-md5' or
|
51
|
+
lk == 'content-type' or
|
52
|
+
lk == 'date' or
|
53
|
+
lk =~ /^#{AMAZON_HEADER_PREFIX}/o)
|
54
|
+
interesting_headers[lk] = value.to_s.strip
|
55
|
+
end
|
56
|
+
end
|
57
|
+
|
58
|
+
# these fields get empty strings if they don't exist.
|
59
|
+
interesting_headers['content-type'] ||= ''
|
60
|
+
interesting_headers['content-md5'] ||= ''
|
61
|
+
|
62
|
+
# just in case someone used this. it's not necessary in this lib.
|
63
|
+
if interesting_headers.has_key? 'x-amz-date'
|
64
|
+
interesting_headers['date'] = ''
|
65
|
+
end
|
66
|
+
|
67
|
+
# if you're using expires for query string auth, then it trumps date
|
68
|
+
# (and x-amz-date)
|
69
|
+
if not expires.nil?
|
70
|
+
interesting_headers['date'] = expires
|
71
|
+
end
|
72
|
+
|
73
|
+
buf = "#{method}\n"
|
74
|
+
interesting_headers.sort { |a, b| a[0] <=> b[0] }.each do |key, value|
|
75
|
+
if key =~ /^#{AMAZON_HEADER_PREFIX}/o
|
76
|
+
buf << "#{key}:#{value}\n"
|
77
|
+
else
|
78
|
+
buf << "#{value}\n"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
# build the path using the bucket and key
|
83
|
+
if not bucket.empty?
|
84
|
+
buf << "/#{bucket}"
|
85
|
+
end
|
86
|
+
# append the key (it might be empty string)
|
87
|
+
# append a slash regardless
|
88
|
+
buf << "/#{path}"
|
89
|
+
|
90
|
+
# if there is an acl, logging, or torrent parameter
|
91
|
+
# add them to the string
|
92
|
+
if path_args.has_key?('acl')
|
93
|
+
buf << '?acl'
|
94
|
+
elsif path_args.has_key?('torrent')
|
95
|
+
buf << '?torrent'
|
96
|
+
elsif path_args.has_key?('logging')
|
97
|
+
buf << '?logging'
|
98
|
+
end
|
99
|
+
|
100
|
+
return buf
|
101
|
+
end
|
102
|
+
|
103
|
+
# encodes the given string with the aws_secret_access_key, by taking the
|
104
|
+
# hmac-sha1 sum, and then base64 encoding it. optionally, it will also
|
105
|
+
# url encode the result of that to protect the string if it's going to
|
106
|
+
# be used as a query string parameter.
|
107
|
+
def S3.encode(aws_secret_access_key, str, urlencode=false)
|
108
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
109
|
+
b64_hmac =
|
110
|
+
Base64.encode64(
|
111
|
+
OpenSSL::HMAC.digest(digest, aws_secret_access_key, str)).strip
|
112
|
+
|
113
|
+
if urlencode
|
114
|
+
return CGI::escape(b64_hmac)
|
115
|
+
else
|
116
|
+
return b64_hmac
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
# build the path_argument string
|
121
|
+
def S3.path_args_hash_to_string(path_args={})
|
122
|
+
arg_string = ''
|
123
|
+
path_args.each { |k, v|
|
124
|
+
arg_string << k.to_s
|
125
|
+
if not v.nil?
|
126
|
+
arg_string << "=#{CGI::escape(v)}"
|
127
|
+
end
|
128
|
+
arg_string << '&'
|
129
|
+
}
|
130
|
+
return arg_string
|
131
|
+
end
|
132
|
+
|
133
|
+
|
134
|
+
# uses Net::HTTP to interface with S3. note that this interface should only
|
135
|
+
# be used for smaller objects, as it does not stream the data. if you were
|
136
|
+
# to download a 1gb file, it would require 1gb of memory. also, this class
|
137
|
+
# creates a new http connection each time. it would be greatly improved with
|
138
|
+
# some connection pooling.
|
139
|
+
class AWSAuthConnection
|
140
|
+
attr_accessor :calling_format
|
141
|
+
|
142
|
+
def initialize(aws_access_key_id,
|
143
|
+
aws_secret_access_key,
|
144
|
+
tokens=Array.new,
|
145
|
+
is_secure=true,
|
146
|
+
server=DEFAULT_HOST,
|
147
|
+
port=PORTS_BY_SECURITY[is_secure],
|
148
|
+
calling_format=CallingFormat::SUBDOMAIN)
|
149
|
+
@aws_access_key_id = aws_access_key_id
|
150
|
+
@aws_secret_access_key = aws_secret_access_key
|
151
|
+
@server = server
|
152
|
+
@is_secure = is_secure
|
153
|
+
@calling_format = calling_format
|
154
|
+
@port = port
|
155
|
+
@init_headers = {}
|
156
|
+
if tokens && !tokens.empty?
|
157
|
+
@init_headers[AMAZON_TOKEN_HEADER_PREFIX] = tokens.join(',')
|
158
|
+
end
|
159
|
+
end
|
160
|
+
|
161
|
+
def create_bucket(bucket, headers={})
|
162
|
+
return Response.new(make_request('PUT', bucket, '', {}, headers))
|
163
|
+
end
|
164
|
+
|
165
|
+
# takes options :prefix, :marker, :max_keys, and :delimiter
|
166
|
+
def list_bucket(bucket, options={}, headers={})
|
167
|
+
path_args = {}
|
168
|
+
options.each { |k, v|
|
169
|
+
path_args[k] = v.to_s
|
170
|
+
}
|
171
|
+
|
172
|
+
return ListBucketResponse.new(make_request('GET', bucket, '', path_args, headers))
|
173
|
+
end
|
174
|
+
|
175
|
+
def delete_bucket(bucket, headers={})
|
176
|
+
return Response.new(make_request('DELETE', bucket, '', {}, headers))
|
177
|
+
end
|
178
|
+
|
179
|
+
def put(bucket, key, object, headers={})
|
180
|
+
object = S3Object.new(object) if not object.instance_of? S3Object
|
181
|
+
|
182
|
+
return Response.new(
|
183
|
+
make_request('PUT', bucket, CGI::escape(key), {}, headers, object.data, object.metadata)
|
184
|
+
)
|
185
|
+
end
|
186
|
+
def copy(source_bucket, source_key, destination_bucket,destination_key, headers={})
|
187
|
+
headers['x-amz-copy-source']="#{source_bucket}/#{source_key}"
|
188
|
+
headers['x-amz-metadata-directive']="REPLACE "
|
189
|
+
return GetResponse.new(make_request('PUT', destination_bucket, CGI::escape(destination_key),{}, headers))
|
190
|
+
end
|
191
|
+
|
192
|
+
def get_head(bucket, key, headers={})
|
193
|
+
return GetResponse.new(make_request('HEAD',bucket, CGI::escape(key),{}, headers))
|
194
|
+
end
|
195
|
+
def get_content_type(bucket, key, headers={})
|
196
|
+
response= get_head(bucket, key, headers)
|
197
|
+
if response.http_response.code=='404'
|
198
|
+
return nil
|
199
|
+
|
200
|
+
elsif response.http_response.code=='200'
|
201
|
+
return response.http_response.header.content_type
|
202
|
+
end
|
203
|
+
raise response.http_response.code
|
204
|
+
end
|
205
|
+
|
206
|
+
|
207
|
+
def get(bucket, key, headers={})
|
208
|
+
return GetResponse.new(make_request('GET', bucket, CGI::escape(key), {}, headers))
|
209
|
+
end
|
210
|
+
|
211
|
+
def delete(bucket, key, headers={})
|
212
|
+
return Response.new(make_request('DELETE', bucket, CGI::escape(key), {}, headers))
|
213
|
+
end
|
214
|
+
|
215
|
+
def get_bucket_logging(bucket, headers={})
|
216
|
+
return GetResponse.new(make_request('GET', bucket, '', {'logging' => nil}, headers))
|
217
|
+
end
|
218
|
+
|
219
|
+
def put_bucket_logging(bucket, logging_xml_doc, headers={})
|
220
|
+
return Response.new(make_request('PUT', bucket, '', {'logging' => nil}, headers, logging_xml_doc))
|
221
|
+
end
|
222
|
+
|
223
|
+
def get_bucket_acl(bucket, headers={})
|
224
|
+
return get_acl(bucket, '', headers)
|
225
|
+
end
|
226
|
+
|
227
|
+
# returns an xml document representing the access control list.
|
228
|
+
# this could be parsed into an object.
|
229
|
+
def get_acl(bucket, key, headers={})
|
230
|
+
return GetResponse.new(make_request('GET', bucket, CGI::escape(key), {'acl' => nil}, headers))
|
231
|
+
end
|
232
|
+
|
233
|
+
def put_bucket_acl(bucket, acl_xml_doc, headers={})
|
234
|
+
return put_acl(bucket, '', acl_xml_doc, headers)
|
235
|
+
end
|
236
|
+
|
237
|
+
# sets the access control policy for the given resource. acl_xml_doc must
|
238
|
+
# be a string in the acl xml format.
|
239
|
+
def put_acl(bucket, key, acl_xml_doc, headers={})
|
240
|
+
return Response.new(
|
241
|
+
make_request('PUT', bucket, CGI::escape(key), {'acl' => nil}, headers, acl_xml_doc, {})
|
242
|
+
)
|
243
|
+
end
|
244
|
+
|
245
|
+
def list_all_my_buckets(headers={})
|
246
|
+
return ListAllMyBucketsResponse.new(make_request('GET', '', '', {}, headers))
|
247
|
+
end
|
248
|
+
|
249
|
+
private
|
250
|
+
def make_request(method, bucket='', key='', path_args={}, headers={}, data='', metadata={})
|
251
|
+
|
252
|
+
# build the domain based on the calling format
|
253
|
+
server = ''
|
254
|
+
if bucket.empty?
|
255
|
+
# for a bucketless request (i.e. list all buckets)
|
256
|
+
# revert to regular domain case since this operation
|
257
|
+
# does not make sense for vanity domains
|
258
|
+
server = @server
|
259
|
+
elsif @calling_format == CallingFormat::SUBDOMAIN
|
260
|
+
server = "#{bucket}.#{@server}"
|
261
|
+
elsif @calling_format == CallingFormat::VANITY
|
262
|
+
server = bucket
|
263
|
+
else
|
264
|
+
server = @server
|
265
|
+
end
|
266
|
+
|
267
|
+
# build the path based on the calling format
|
268
|
+
path = ''
|
269
|
+
if (not bucket.empty?) and (@calling_format == CallingFormat::REGULAR)
|
270
|
+
path << "/#{bucket}"
|
271
|
+
end
|
272
|
+
# add the slash after the bucket regardless
|
273
|
+
# the key will be appended if it is non-empty
|
274
|
+
path << "/#{key}"
|
275
|
+
|
276
|
+
# build the path_argument string
|
277
|
+
# add the ? in all cases since
|
278
|
+
# signature and credentials follow path args
|
279
|
+
path << '?'
|
280
|
+
path << S3.path_args_hash_to_string(path_args)
|
281
|
+
|
282
|
+
http = Net::HTTP.new(server, @port)
|
283
|
+
http.use_ssl = @is_secure
|
284
|
+
http.start do
|
285
|
+
req = method_to_request_class(method).new("#{path}")
|
286
|
+
|
287
|
+
set_headers(req, @init_headers)
|
288
|
+
set_headers(req, headers)
|
289
|
+
set_headers(req, metadata, METADATA_PREFIX)
|
290
|
+
|
291
|
+
set_aws_auth_header(req, @aws_access_key_id, @aws_secret_access_key, bucket, key, path_args)
|
292
|
+
if req.request_body_permitted?
|
293
|
+
return http.request(req, data)
|
294
|
+
else
|
295
|
+
return http.request(req)
|
296
|
+
end
|
297
|
+
end
|
298
|
+
|
299
|
+
end
|
300
|
+
|
301
|
+
def method_to_request_class(method)
|
302
|
+
case method
|
303
|
+
when 'GET'
|
304
|
+
return Net::HTTP::Get
|
305
|
+
when 'PUT'
|
306
|
+
return Net::HTTP::Put
|
307
|
+
when 'DELETE'
|
308
|
+
return Net::HTTP::Delete
|
309
|
+
when 'HEAD'
|
310
|
+
return Net::HTTP::Head
|
311
|
+
else
|
312
|
+
raise "Unsupported method #{method}"
|
313
|
+
end
|
314
|
+
end
|
315
|
+
|
316
|
+
# set the Authorization header using AWS signed header authentication
|
317
|
+
def set_aws_auth_header(request, aws_access_key_id, aws_secret_access_key, bucket='', key='', path_args={})
|
318
|
+
# we want to fix the date here if it's not already been done.
|
319
|
+
request['Date'] ||= Time.now.httpdate
|
320
|
+
|
321
|
+
# ruby will automatically add a random content-type on some verbs, so
|
322
|
+
# here we add a dummy one to 'suppress' it. change this logic if having
|
323
|
+
# an empty content-type header becomes semantically meaningful for any
|
324
|
+
# other verb.
|
325
|
+
request['Content-Type'] ||= ''
|
326
|
+
|
327
|
+
canonical_string =
|
328
|
+
S3.canonical_string(request.method, bucket, key, path_args, request.to_hash, nil)
|
329
|
+
encoded_canonical = S3.encode(aws_secret_access_key, canonical_string)
|
330
|
+
|
331
|
+
request['Authorization'] = "AWS #{aws_access_key_id}:#{encoded_canonical}"
|
332
|
+
end
|
333
|
+
|
334
|
+
def set_headers(request, headers, prefix='')
|
335
|
+
headers.each do |key, value|
|
336
|
+
request[prefix + key] = value
|
337
|
+
end
|
338
|
+
end
|
339
|
+
end
|
340
|
+
|
341
|
+
|
342
|
+
|
343
|
+
class S3Object
|
344
|
+
attr_accessor :data
|
345
|
+
attr_accessor :metadata
|
346
|
+
def initialize(data, metadata={})
|
347
|
+
@data, @metadata = data, metadata
|
348
|
+
end
|
349
|
+
end
|
350
|
+
|
351
|
+
# class for storing calling format constants
|
352
|
+
module CallingFormat
|
353
|
+
REGULAR = 0 # http://s3.amazonaws.com/bucket/key
|
354
|
+
SUBDOMAIN = 1 # http://bucket.s3.amazonaws.com/key
|
355
|
+
VANITY = 2 # http://<vanity_domain>/key -- vanity_domain resolves to s3.amazonaws.com
|
356
|
+
|
357
|
+
# build the url based on the calling format, and bucket
|
358
|
+
def CallingFormat.build_url_base(protocol, server, port, bucket, format)
|
359
|
+
build_url_base = "#{protocol}://"
|
360
|
+
if bucket.empty?
|
361
|
+
build_url_base << "#{server}:#{port}"
|
362
|
+
elsif format == SUBDOMAIN
|
363
|
+
build_url_base << "#{bucket}.#{server}:#{port}"
|
364
|
+
elsif format == VANITY
|
365
|
+
build_url_base << "#{bucket}:#{port}"
|
366
|
+
else
|
367
|
+
build_url_base << "#{server}:#{port}/#{bucket}"
|
368
|
+
end
|
369
|
+
return build_url_base
|
370
|
+
end
|
371
|
+
end
|
372
|
+
|
373
|
+
class Owner
|
374
|
+
attr_accessor :id
|
375
|
+
attr_accessor :display_name
|
376
|
+
end
|
377
|
+
|
378
|
+
class ListEntry
|
379
|
+
attr_accessor :key
|
380
|
+
attr_accessor :last_modified
|
381
|
+
attr_accessor :etag
|
382
|
+
attr_accessor :size
|
383
|
+
attr_accessor :storage_class
|
384
|
+
attr_accessor :owner
|
385
|
+
end
|
386
|
+
|
387
|
+
class ListProperties
|
388
|
+
attr_accessor :name
|
389
|
+
attr_accessor :prefix
|
390
|
+
attr_accessor :marker
|
391
|
+
attr_accessor :max_keys
|
392
|
+
attr_accessor :delimiter
|
393
|
+
attr_accessor :is_truncated
|
394
|
+
attr_accessor :next_marker
|
395
|
+
end
|
396
|
+
|
397
|
+
class CommonPrefixEntry
|
398
|
+
attr_accessor :prefix
|
399
|
+
end
|
400
|
+
|
401
|
+
# Parses the list bucket output into a list of ListEntry objects, and
|
402
|
+
# a list of CommonPrefixEntry objects if applicable.
|
403
|
+
class ListBucketParser
|
404
|
+
attr_reader :properties
|
405
|
+
attr_reader :entries
|
406
|
+
attr_reader :common_prefixes
|
407
|
+
|
408
|
+
def initialize
|
409
|
+
reset
|
410
|
+
end
|
411
|
+
|
412
|
+
def tag_start(name, attributes)
|
413
|
+
if name == 'ListBucketResult'
|
414
|
+
@properties = ListProperties.new
|
415
|
+
elsif name == 'Contents'
|
416
|
+
@curr_entry = ListEntry.new
|
417
|
+
elsif name == 'Owner'
|
418
|
+
@curr_entry.owner = Owner.new
|
419
|
+
elsif name == 'CommonPrefixes'
|
420
|
+
@common_prefix_entry = CommonPrefixEntry.new
|
421
|
+
end
|
422
|
+
end
|
423
|
+
|
424
|
+
# we have one, add him to the entries list
|
425
|
+
def tag_end(name)
|
426
|
+
# this prefix is the one we echo back from the request
|
427
|
+
if name == 'Name'
|
428
|
+
@properties.name = @curr_text
|
429
|
+
elsif name == 'Prefix' and @is_echoed_prefix
|
430
|
+
@properties.prefix = @curr_text
|
431
|
+
@is_echoed_prefix = nil
|
432
|
+
elsif name == 'Marker'
|
433
|
+
@properties.marker = @curr_text
|
434
|
+
elsif name == 'MaxKeys'
|
435
|
+
@properties.max_keys = @curr_text.to_i
|
436
|
+
elsif name == 'Delimiter'
|
437
|
+
@properties.delimiter = @curr_text
|
438
|
+
elsif name == 'IsTruncated'
|
439
|
+
@properties.is_truncated = @curr_text == 'true'
|
440
|
+
elsif name == 'NextMarker'
|
441
|
+
@properties.next_marker = @curr_text
|
442
|
+
elsif name == 'Contents'
|
443
|
+
@entries << @curr_entry
|
444
|
+
elsif name == 'Key'
|
445
|
+
@curr_entry.key = @curr_text
|
446
|
+
elsif name == 'LastModified'
|
447
|
+
@curr_entry.last_modified = @curr_text
|
448
|
+
elsif name == 'ETag'
|
449
|
+
@curr_entry.etag = @curr_text
|
450
|
+
elsif name == 'Size'
|
451
|
+
@curr_entry.size = @curr_text.to_i
|
452
|
+
elsif name == 'StorageClass'
|
453
|
+
@curr_entry.storage_class = @curr_text
|
454
|
+
elsif name == 'ID'
|
455
|
+
@curr_entry.owner.id = @curr_text
|
456
|
+
elsif name == 'DisplayName'
|
457
|
+
@curr_entry.owner.display_name = @curr_text
|
458
|
+
elsif name == 'CommonPrefixes'
|
459
|
+
@common_prefixes << @common_prefix_entry
|
460
|
+
elsif name == 'Prefix'
|
461
|
+
# this is the common prefix for keys that match up to the delimiter
|
462
|
+
@common_prefix_entry.prefix = @curr_text
|
463
|
+
end
|
464
|
+
@curr_text = ''
|
465
|
+
end
|
466
|
+
|
467
|
+
def text(text)
|
468
|
+
@curr_text += text
|
469
|
+
end
|
470
|
+
|
471
|
+
def xmldecl(version, encoding, standalone)
|
472
|
+
# ignore
|
473
|
+
end
|
474
|
+
|
475
|
+
# get ready for another parse
|
476
|
+
def reset
|
477
|
+
@is_echoed_prefix = true;
|
478
|
+
@entries = []
|
479
|
+
@curr_entry = nil
|
480
|
+
@common_prefixes = []
|
481
|
+
@common_prefix_entry = nil
|
482
|
+
@curr_text = ''
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
class Bucket
|
487
|
+
attr_accessor :name
|
488
|
+
attr_accessor :creation_date
|
489
|
+
end
|
490
|
+
|
491
|
+
class ListAllMyBucketsParser
|
492
|
+
attr_reader :entries
|
493
|
+
|
494
|
+
def initialize
|
495
|
+
reset
|
496
|
+
end
|
497
|
+
|
498
|
+
def tag_start(name, attributes)
|
499
|
+
if name == 'Bucket'
|
500
|
+
@curr_bucket = Bucket.new
|
501
|
+
end
|
502
|
+
end
|
503
|
+
|
504
|
+
# we have one, add him to the entries list
|
505
|
+
def tag_end(name)
|
506
|
+
if name == 'Bucket'
|
507
|
+
@entries << @curr_bucket
|
508
|
+
elsif name == 'Name'
|
509
|
+
@curr_bucket.name = @curr_text
|
510
|
+
elsif name == 'CreationDate'
|
511
|
+
@curr_bucket.creation_date = @curr_text
|
512
|
+
end
|
513
|
+
@curr_text = ''
|
514
|
+
end
|
515
|
+
|
516
|
+
def text(text)
|
517
|
+
@curr_text += text
|
518
|
+
end
|
519
|
+
|
520
|
+
def xmldecl(version, encoding, standalone)
|
521
|
+
# ignore
|
522
|
+
end
|
523
|
+
|
524
|
+
# get ready for another parse
|
525
|
+
def reset
|
526
|
+
@entries = []
|
527
|
+
@owner = nil
|
528
|
+
@curr_bucket = nil
|
529
|
+
@curr_text = ''
|
530
|
+
end
|
531
|
+
end
|
532
|
+
|
533
|
+
class Response
|
534
|
+
attr_reader :http_response
|
535
|
+
def initialize(response)
|
536
|
+
@http_response = response
|
537
|
+
end
|
538
|
+
end
|
539
|
+
|
540
|
+
class GetResponse < Response
|
541
|
+
attr_reader :object
|
542
|
+
def initialize(response)
|
543
|
+
super(response)
|
544
|
+
metadata = get_aws_metadata(response)
|
545
|
+
data = response.body
|
546
|
+
@object = S3Object.new(data, metadata)
|
547
|
+
end
|
548
|
+
|
549
|
+
# parses the request headers and pulls out the s3 metadata into a hash
|
550
|
+
def get_aws_metadata(response)
|
551
|
+
metadata = {}
|
552
|
+
response.each do |key, value|
|
553
|
+
if key =~ /^#{METADATA_PREFIX}(.*)$/oi
|
554
|
+
metadata[$1] = value
|
555
|
+
end
|
556
|
+
end
|
557
|
+
return metadata
|
558
|
+
end
|
559
|
+
end
|
560
|
+
|
561
|
+
class ListBucketResponse < Response
|
562
|
+
attr_reader :properties
|
563
|
+
attr_reader :entries
|
564
|
+
attr_reader :common_prefix_entries
|
565
|
+
|
566
|
+
def initialize(response)
|
567
|
+
super(response)
|
568
|
+
if response.is_a? Net::HTTPSuccess
|
569
|
+
parser = ListBucketParser.new
|
570
|
+
REXML::Document.parse_stream(response.body, parser)
|
571
|
+
@properties = parser.properties
|
572
|
+
@entries = parser.entries
|
573
|
+
@common_prefix_entries = parser.common_prefixes
|
574
|
+
else
|
575
|
+
@entries = []
|
576
|
+
end
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
class ListAllMyBucketsResponse < Response
|
581
|
+
attr_reader :entries
|
582
|
+
def initialize(response)
|
583
|
+
super(response)
|
584
|
+
if response.is_a? Net::HTTPSuccess
|
585
|
+
parser = ListAllMyBucketsParser.new
|
586
|
+
REXML::Document.parse_stream(response.body, parser)
|
587
|
+
@entries = parser.entries
|
588
|
+
else
|
589
|
+
@entries = []
|
590
|
+
end
|
591
|
+
end
|
592
|
+
end
|
593
|
+
end
|
594
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module SdbDal
|
2
|
+
|
3
|
+
|
4
|
+
module SdbFormatter
|
5
|
+
def parse_reference_set(value)
|
6
|
+
result=YAML::load(value)
|
7
|
+
return result if(result.class==Reference)
|
8
|
+
return
|
9
|
+
end
|
10
|
+
|
11
|
+
def parse_date(value)
|
12
|
+
return nil if value==nil
|
13
|
+
if value.is_a? Date
|
14
|
+
return value
|
15
|
+
end
|
16
|
+
if value.is_a? Time
|
17
|
+
return value
|
18
|
+
end
|
19
|
+
return nil if value.length==0
|
20
|
+
return Time.at(value.to_f)
|
21
|
+
end
|
22
|
+
def parse_boolean(value)
|
23
|
+
return nil if value==nil
|
24
|
+
if value=="true"
|
25
|
+
return true
|
26
|
+
else
|
27
|
+
return false
|
28
|
+
end
|
29
|
+
end
|
30
|
+
def parse_integer(value)
|
31
|
+
return nil if value==nil
|
32
|
+
return value.to_i
|
33
|
+
|
34
|
+
end
|
35
|
+
def parse_float(value)
|
36
|
+
return nil if value==nil
|
37
|
+
return value.to_f
|
38
|
+
end
|
39
|
+
def parse_unsigned_integer(value)
|
40
|
+
return nil if value==nil
|
41
|
+
return value.to_i
|
42
|
+
end
|
43
|
+
|
44
|
+
def format_reference_set(value)
|
45
|
+
value.to_yaml
|
46
|
+
|
47
|
+
end
|
48
|
+
|
49
|
+
def format_date(value)
|
50
|
+
return nil if value==nil
|
51
|
+
return value.to_f.to_s
|
52
|
+
end
|
53
|
+
def format_boolean(value)
|
54
|
+
|
55
|
+
return ((value == true) || (value==1))?'true':'false'
|
56
|
+
end
|
57
|
+
def format_integer(value)
|
58
|
+
|
59
|
+
|
60
|
+
if value==nil
|
61
|
+
return nil
|
62
|
+
end
|
63
|
+
return zero_pad_integer(value)
|
64
|
+
# return nil if value==nil
|
65
|
+
# sign='p'
|
66
|
+
# if value<0
|
67
|
+
# sign='n'
|
68
|
+
# end
|
69
|
+
# return sign+zero_pad_integer(Math.abs(value))
|
70
|
+
|
71
|
+
end
|
72
|
+
def format_string(value)
|
73
|
+
if value==nil
|
74
|
+
return nil
|
75
|
+
end
|
76
|
+
|
77
|
+
if value.length>1024
|
78
|
+
value=value[0..1023]
|
79
|
+
end
|
80
|
+
value
|
81
|
+
end
|
82
|
+
def format_float(value)
|
83
|
+
if value==nil
|
84
|
+
return nil
|
85
|
+
end
|
86
|
+
return zero_pad_float(value)
|
87
|
+
end
|
88
|
+
def format_unsigned_integer(value)
|
89
|
+
if !value
|
90
|
+
return nil
|
91
|
+
end
|
92
|
+
return zero_pad_integer(value)
|
93
|
+
end
|
94
|
+
|
95
|
+
|
96
|
+
|
97
|
+
def zero_pad_integer(value)
|
98
|
+
value=value.to_i
|
99
|
+
temp= value.abs.to_s.rjust(30,"0")
|
100
|
+
if value>=0
|
101
|
+
temp="0"+temp
|
102
|
+
else
|
103
|
+
|
104
|
+
temp="-"+temp
|
105
|
+
end
|
106
|
+
return temp
|
107
|
+
end
|
108
|
+
def zero_pad_float(value)
|
109
|
+
temp= value.abs.to_s.rjust(30,"0")
|
110
|
+
if value>=0
|
111
|
+
temp="0"+temp
|
112
|
+
else
|
113
|
+
|
114
|
+
temp="-"+temp
|
115
|
+
end
|
116
|
+
return temp
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|