uber-s3 0.1.1
Sign up to get free protection for your applications and to get access to all the features.
- data/README.md +122 -0
- data/lib/uber-s3.rb +35 -0
- data/lib/uber-s3/authorization.rb +29 -0
- data/lib/uber-s3/bucket.rb +99 -0
- data/lib/uber-s3/connection.rb +62 -0
- data/lib/uber-s3/connection/em_http.rb +13 -0
- data/lib/uber-s3/connection/em_http_fibered.rb +24 -0
- data/lib/uber-s3/connection/net_http.rb +23 -0
- data/lib/uber-s3/exceptions.rb +7 -0
- data/lib/uber-s3/object.rb +130 -0
- data/lib/uber-s3/operation.rb +22 -0
- data/lib/uber-s3/operation/object/access_policy.rb +20 -0
- data/lib/uber-s3/operation/object/cache_control.rb +19 -0
- data/lib/uber-s3/operation/object/content_disposition.rb +19 -0
- data/lib/uber-s3/operation/object/content_encoding.rb +19 -0
- data/lib/uber-s3/operation/object/content_md5.rb +19 -0
- data/lib/uber-s3/operation/object/content_type.rb +19 -0
- data/lib/uber-s3/operation/object/expires.rb +19 -0
- data/lib/uber-s3/operation/object/meta.rb +19 -0
- data/lib/uber-s3/operation/object/storage_class.rb +19 -0
- data/lib/uber-s3/util/xml_document.rb +48 -0
- data/lib/uber-s3/version.rb +3 -0
- metadata +99 -0
data/README.md
ADDED
@@ -0,0 +1,122 @@
|
|
1
|
+
# Uber-S3
|
2
|
+
|
3
|
+
A simple, but very fast, S3 client written in Ruby supporting
|
4
|
+
synchronous and asynchronous HTTP communication.
|
5
|
+
|
6
|
+
## Examples
|
7
|
+
|
8
|
+
```ruby
|
9
|
+
require 'uber-s3'
|
10
|
+
|
11
|
+
##########################################################################
|
12
|
+
# Connecting to S3
|
13
|
+
# adapter can be :net_http or :em_http_fibered
|
14
|
+
s3 = UberS3.new({
|
15
|
+
:access_key => 'abc',
|
16
|
+
:secret_access_key => 'def',
|
17
|
+
:bucket => 'funbucket',
|
18
|
+
:persistent => true,
|
19
|
+
:adapter => :em_http_fibered
|
20
|
+
})
|
21
|
+
|
22
|
+
|
23
|
+
|
24
|
+
##########################################################################
|
25
|
+
# Saving objects
|
26
|
+
s3.store('/test.txt', 'Look ma no hands')
|
27
|
+
|
28
|
+
o = s3.object('/test.txt')
|
29
|
+
o.value = 'Look ma no hands'
|
30
|
+
o.save
|
31
|
+
|
32
|
+
# or..
|
33
|
+
|
34
|
+
o = UberS3::Object.new(client.bucket, '/test.txt', 'heyo')
|
35
|
+
o.save # => true
|
36
|
+
|
37
|
+
|
38
|
+
|
39
|
+
##########################################################################
|
40
|
+
# Reading objects
|
41
|
+
s3['/test.txt'].class # => UberS3::Object
|
42
|
+
s3['/test.txt'].value # => 'heyo'
|
43
|
+
s3.get('/test.txt').value # => 'heyo'
|
44
|
+
|
45
|
+
s3.exists?('/anotherone') # => false
|
46
|
+
|
47
|
+
|
48
|
+
|
49
|
+
##########################################################################
|
50
|
+
# Object access control
|
51
|
+
|
52
|
+
o.access = :private
|
53
|
+
o.access = :public_read
|
54
|
+
# etc.
|
55
|
+
|
56
|
+
# Valid options:
|
57
|
+
# :private, :public_read, :public_read_write, :authenticated_read
|
58
|
+
|
59
|
+
# See http://docs.amazonwebservices.com/AmazonS3/2006-03-01/dev/index.html?RESTAccessPolicy.html
|
60
|
+
# NOTE: default object access level is :private
|
61
|
+
|
62
|
+
|
63
|
+
|
64
|
+
##########################################################################
|
65
|
+
# Deleting objects
|
66
|
+
o.delete # => true
|
67
|
+
|
68
|
+
|
69
|
+
##########################################################################
|
70
|
+
# Save optional parameters
|
71
|
+
# See http://docs.amazonwebservices.com/AmazonS3/latest/API/index.html?RESTObjectPUT.html
|
72
|
+
|
73
|
+
options = { :access => :public_read, :content_type => 'text/plain' }
|
74
|
+
o = UberS3::Object.new(client.bucket, '/test.txt', 'heyo', options)
|
75
|
+
o.save
|
76
|
+
|
77
|
+
# or..
|
78
|
+
|
79
|
+
o = s3.object('/test.txt')
|
80
|
+
o.value = 'Look ma no hands'
|
81
|
+
o.access = :public_read
|
82
|
+
o.content_type = 'text/plain'
|
83
|
+
o.save
|
84
|
+
|
85
|
+
# List of parameter methods:
|
86
|
+
# :access -- Object access control
|
87
|
+
# :cache_control -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.9
|
88
|
+
# :content_disposition -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec19.html#sec19.5.1
|
89
|
+
# :content_encoding -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.11
|
90
|
+
# :content_md5 -- End-to-end integrity check
|
91
|
+
# :content_type -- http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html#sec14.17
|
92
|
+
# :expires -- Number of milliseconds before expiration
|
93
|
+
# :storage_class -- Amazon S3's storage levels (redundancy for price)
|
94
|
+
|
95
|
+
|
96
|
+
##########################################################################
|
97
|
+
# Iterating objects in a bucket
|
98
|
+
s3.objects('/path').each {|obj| puts obj }
|
99
|
+
|
100
|
+
```
|
101
|
+
|
102
|
+
## Ruby version notes
|
103
|
+
|
104
|
+
* Tested on MRI 1.9.2 (net_http / em_http_fibered adapters) -- recommended
|
105
|
+
* Ruby 1.8.7 works for net/http clients, em_http_fibered adapter requires fibers (duh)
|
106
|
+
* JRuby in 1.9 mode + em_http_fibered adapter should work, but held back because of issues with EM
|
107
|
+
|
108
|
+
## Other notes
|
109
|
+
|
110
|
+
* If Nokogiri is available, it will use it instead of REXML
|
111
|
+
|
112
|
+
## TODO
|
113
|
+
|
114
|
+
* Refactor UberS3::Object class, consider putting the operations/headers into separate classes
|
115
|
+
* Better exception handling and reporting
|
116
|
+
* Query string authentication -- neat feature for providing temporary public access to a private object
|
117
|
+
* Object versioning support
|
118
|
+
|
119
|
+
## S3 API Docs
|
120
|
+
|
121
|
+
- S3 REST API: http://docs.amazonwebservices.com/AmazonS3/latest/API/
|
122
|
+
- S3 Request Authorization: http://docs.amazonwebservices.com/AmazonS3/latest/dev/RESTAuthentication.html
|
data/lib/uber-s3.rb
ADDED
@@ -0,0 +1,35 @@
|
|
1
|
+
require 'cgi'
|
2
|
+
require 'time'
|
3
|
+
require 'openssl'
|
4
|
+
require 'forwardable'
|
5
|
+
require 'base64'
|
6
|
+
require 'digest/md5'
|
7
|
+
|
8
|
+
class UberS3
|
9
|
+
extend Forwardable
|
10
|
+
|
11
|
+
attr_accessor :connection, :bucket
|
12
|
+
def_delegators :@bucket, :store, :set, :object, :get, :[], :exists?, :objects
|
13
|
+
|
14
|
+
def initialize(options={})
|
15
|
+
self.connection = Connection.open(self, options)
|
16
|
+
self.bucket = options[:bucket]
|
17
|
+
end
|
18
|
+
|
19
|
+
def inspect
|
20
|
+
"#<UberS3 client v#{UberS3::VERSION}>"
|
21
|
+
end
|
22
|
+
|
23
|
+
def bucket=(bucket)
|
24
|
+
@bucket = bucket.is_a?(Bucket) ? bucket : Bucket.new(self, bucket)
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
require 'uber-s3/version'
|
29
|
+
require 'uber-s3/exceptions'
|
30
|
+
require 'uber-s3/connection'
|
31
|
+
require 'uber-s3/authorization'
|
32
|
+
require 'uber-s3/bucket'
|
33
|
+
require 'uber-s3/object'
|
34
|
+
|
35
|
+
require 'uber-s3/util/xml_document'
|
@@ -0,0 +1,29 @@
|
|
1
|
+
class UberS3
|
2
|
+
module Authorization
|
3
|
+
|
4
|
+
def sign(client, verb, path, headers={})
|
5
|
+
req_verb = verb.to_s.upcase
|
6
|
+
req_content_md5 = headers['Content-MD5']
|
7
|
+
req_content_type = headers['Content-Type']
|
8
|
+
req_date = headers['Date']
|
9
|
+
req_canonical_resource = "/#{client.bucket}/#{path}".split('?').first
|
10
|
+
req_canonical_amz_headers = ''
|
11
|
+
|
12
|
+
headers.keys.select {|k| k =~ /^x-amz-acl/ }.each do |amz_key|
|
13
|
+
req_canonical_amz_headers << amz_key+':'+headers[amz_key]+"\n"
|
14
|
+
end
|
15
|
+
|
16
|
+
canonical_string_to_sign = "#{req_verb}\n"+
|
17
|
+
"#{req_content_md5}\n"+
|
18
|
+
"#{req_content_type}\n"+
|
19
|
+
"#{req_date}\n"+
|
20
|
+
"#{req_canonical_amz_headers}"+
|
21
|
+
"#{req_canonical_resource}"
|
22
|
+
|
23
|
+
digest = OpenSSL::Digest::Digest.new('sha1')
|
24
|
+
[OpenSSL::HMAC.digest(digest, client.connection.secret_access_key, canonical_string_to_sign)].pack("m").strip
|
25
|
+
end
|
26
|
+
|
27
|
+
extend self
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
class UberS3
|
2
|
+
class Bucket
|
3
|
+
|
4
|
+
attr_accessor :client, :name
|
5
|
+
|
6
|
+
def initialize(client, name)
|
7
|
+
self.client = client
|
8
|
+
self.name = name
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_s
|
12
|
+
name.to_s
|
13
|
+
end
|
14
|
+
|
15
|
+
def connection
|
16
|
+
client.connection
|
17
|
+
end
|
18
|
+
|
19
|
+
def store(key, value, options={})
|
20
|
+
Object.new(self, key, value, options).save
|
21
|
+
end
|
22
|
+
alias_method :set, :store
|
23
|
+
|
24
|
+
def object(key)
|
25
|
+
Object.new(self, key)
|
26
|
+
end
|
27
|
+
|
28
|
+
def get(key)
|
29
|
+
object(key).fetch
|
30
|
+
end
|
31
|
+
alias_method :[], :get
|
32
|
+
|
33
|
+
def exists?(key)
|
34
|
+
object(key).exists?
|
35
|
+
end
|
36
|
+
|
37
|
+
def objects(key, options={})
|
38
|
+
ObjectList.new(self, key, options)
|
39
|
+
end
|
40
|
+
|
41
|
+
|
42
|
+
class ObjectList
|
43
|
+
attr_accessor :bucket, :key, :options, :objects
|
44
|
+
|
45
|
+
def initialize(bucket, key, options={})
|
46
|
+
self.bucket = bucket
|
47
|
+
self.key = key.gsub(/^\//, '')
|
48
|
+
self.options = options
|
49
|
+
self.objects = []
|
50
|
+
end
|
51
|
+
|
52
|
+
def fetch(marker=nil)
|
53
|
+
@objects = []
|
54
|
+
|
55
|
+
response = bucket.connection.get("/?prefix=#{CGI.escape(key)}&marker=#{marker}")
|
56
|
+
# response = bucket.connection.get("/?prefix=#{CGI.escape(key)}&marker=#{marker}&max-keys=2")
|
57
|
+
|
58
|
+
if response[:status] != 200
|
59
|
+
raise UberS3Error
|
60
|
+
else
|
61
|
+
@objects = parse_contents(response[:body])
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def parse_contents(xml)
|
66
|
+
objects = []
|
67
|
+
doc = Util::XmlDocument.new(xml)
|
68
|
+
|
69
|
+
# TODO: can use more error checking on the xml stuff
|
70
|
+
|
71
|
+
@is_truncated = doc.xpath('//ListBucketResult/IsTruncated').first.text == "true"
|
72
|
+
contents = doc.xpath('//ListBucketResult/Contents')
|
73
|
+
|
74
|
+
contents.each do |content|
|
75
|
+
h = {}
|
76
|
+
content.elements.each {|el| h[el.name] = el.text }
|
77
|
+
objects << ::UberS3::Object.new(bucket, h['Key'], nil, { :size => h['Size'].to_i })
|
78
|
+
end if contents.any?
|
79
|
+
|
80
|
+
objects
|
81
|
+
end
|
82
|
+
|
83
|
+
def each(&block)
|
84
|
+
loop do
|
85
|
+
marker = objects.last.key rescue nil
|
86
|
+
fetch(marker)
|
87
|
+
|
88
|
+
objects.each {|obj| block.call(obj) }
|
89
|
+
break if @is_truncated == false
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
def to_a
|
94
|
+
fetch
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class UberS3
|
2
|
+
module Connection
|
3
|
+
|
4
|
+
def self.open(client, options={})
|
5
|
+
adapter = options.delete(:adapter) || :net_http
|
6
|
+
|
7
|
+
begin
|
8
|
+
require "uber-s3/connection/#{adapter}"
|
9
|
+
klass = instance_eval(adapter.to_s.split('_').map {|x| x.capitalize}.join(""))
|
10
|
+
rescue LoadError
|
11
|
+
raise "Cannot load #{adapter} adapter class"
|
12
|
+
end
|
13
|
+
|
14
|
+
klass.new(client, options)
|
15
|
+
end
|
16
|
+
|
17
|
+
|
18
|
+
class Adapter
|
19
|
+
|
20
|
+
attr_accessor :client, :access_key, :secret_access_key, :persistent
|
21
|
+
|
22
|
+
def initialize(client, options={})
|
23
|
+
self.client = client
|
24
|
+
self.access_key = options[:access_key]
|
25
|
+
self.secret_access_key = options[:secret_access_key]
|
26
|
+
self.persistent = options[:persistent] || true
|
27
|
+
end
|
28
|
+
|
29
|
+
[:get, :post, :put, :delete, :head].each do |verb|
|
30
|
+
define_method(verb) do |*args|
|
31
|
+
path, headers, body = args
|
32
|
+
path = path.gsub(/^\//, '')
|
33
|
+
headers ||= {}
|
34
|
+
|
35
|
+
# Default headers
|
36
|
+
headers['Date'] = Time.now.httpdate if !headers.keys.include?('Date')
|
37
|
+
headers['User-Agent'] ||= "UberS3 v#{UberS3::VERSION}"
|
38
|
+
headers['Connection'] = (persistent ? 'keep-alive' : 'close')
|
39
|
+
|
40
|
+
if body
|
41
|
+
headers['Content-Type'] ||= 'application/octet-stream'
|
42
|
+
headers['Content-Length'] ||= body.bytesize.to_s
|
43
|
+
end
|
44
|
+
|
45
|
+
# Authorize the request
|
46
|
+
signature = Authorization.sign(client, verb, path, headers)
|
47
|
+
headers['Authorization'] = "AWS #{access_key}:#{signature}"
|
48
|
+
|
49
|
+
# Make the request
|
50
|
+
url = "http://#{client.bucket}.s3.amazonaws.com/#{path}"
|
51
|
+
request(verb, url, headers, body)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
def request(verb, url, headers={}, body=nil)
|
56
|
+
raise "Abstract method"
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'em-http'
|
3
|
+
|
4
|
+
module UberS3::Connection
|
5
|
+
class EmHttp < Adapter
|
6
|
+
|
7
|
+
# NOTE: this will be very difficult to support
|
8
|
+
# with our interface.. will need lots of work
|
9
|
+
# perhaps will limit async capabilities to
|
10
|
+
# fibered mode
|
11
|
+
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
require 'eventmachine'
|
2
|
+
require 'em-http'
|
3
|
+
require 'em-synchrony'
|
4
|
+
require 'em-synchrony/em-http'
|
5
|
+
|
6
|
+
module UberS3::Connection
|
7
|
+
class EmHttpFibered < Adapter
|
8
|
+
|
9
|
+
def request(verb, url, headers={}, body=nil)
|
10
|
+
params = {}
|
11
|
+
params[:head] = headers
|
12
|
+
params[:body] = body if body
|
13
|
+
|
14
|
+
r = EM::HttpRequest.new(url).send(verb, params)
|
15
|
+
|
16
|
+
{
|
17
|
+
:status => r.response_header.status,
|
18
|
+
:header => r.response_header,
|
19
|
+
:body => r.response
|
20
|
+
}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
require 'net/http'
|
2
|
+
|
3
|
+
module UberS3::Connection
|
4
|
+
class NetHttp < Adapter
|
5
|
+
|
6
|
+
def request(verb, url, headers={}, body=nil)
|
7
|
+
uri = URI.parse(url)
|
8
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
9
|
+
|
10
|
+
req_klass = instance_eval("Net::HTTP::"+verb.to_s.capitalize)
|
11
|
+
req = req_klass.new(uri.to_s, headers)
|
12
|
+
|
13
|
+
r = http.request(req, body)
|
14
|
+
|
15
|
+
{
|
16
|
+
:status => r.code.to_i,
|
17
|
+
:header => r.header.to_hash,
|
18
|
+
:body => r.body
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
class UberS3
|
2
|
+
class Object
|
3
|
+
|
4
|
+
attr_accessor :bucket, :key, :value, :errors
|
5
|
+
# attr_accessor :etag, :last_modified
|
6
|
+
|
7
|
+
attr_accessor :access,
|
8
|
+
:cache_control,
|
9
|
+
:content_disposition,
|
10
|
+
:content_encoding,
|
11
|
+
:size,
|
12
|
+
:content_md5,
|
13
|
+
:content_type,
|
14
|
+
:expires,
|
15
|
+
:storage_class
|
16
|
+
|
17
|
+
def initialize(bucket, key, value=nil, options={})
|
18
|
+
self.bucket = bucket
|
19
|
+
self.key = key
|
20
|
+
self.value = value
|
21
|
+
|
22
|
+
options.each {|k,v| self.send((k.to_s+'=').to_sym, v) }
|
23
|
+
end
|
24
|
+
|
25
|
+
def to_s
|
26
|
+
"#<#{self.class} @key=\"#{self.key}\">"
|
27
|
+
end
|
28
|
+
|
29
|
+
def exists?
|
30
|
+
bucket.connection.head(key)[:status] == 200
|
31
|
+
end
|
32
|
+
|
33
|
+
def fetch
|
34
|
+
self.value = bucket.connection.get(key)[:body]
|
35
|
+
self
|
36
|
+
end
|
37
|
+
|
38
|
+
def save
|
39
|
+
headers = {}
|
40
|
+
|
41
|
+
# Standard pass through values
|
42
|
+
headers['Cache-Control'] = cache_control
|
43
|
+
headers['Content-Disposition'] = content_disposition
|
44
|
+
headers['Content-Encoding'] = content_encoding
|
45
|
+
headers['Content-Length'] = size.to_s
|
46
|
+
headers['Content-MD5'] = content_md5
|
47
|
+
headers['Content-Type'] = content_type
|
48
|
+
headers['Expires'] = expires
|
49
|
+
|
50
|
+
headers.each {|k,v| headers.delete(k) if v.nil? || v.empty? }
|
51
|
+
|
52
|
+
# Content MD5 integrity check
|
53
|
+
if !content_md5.nil?
|
54
|
+
# Our object expects a md5 hex digest
|
55
|
+
md5_digest = content_md5.unpack('a2'*16).collect {|i| i.hex.chr }.join
|
56
|
+
headers['Content-MD5'] = Base64.encode64(md5_digest).strip
|
57
|
+
end
|
58
|
+
|
59
|
+
# ACL
|
60
|
+
if !access.nil?
|
61
|
+
headers['x-amz-acl'] = access.to_s.gsub('_', '-')
|
62
|
+
end
|
63
|
+
|
64
|
+
# Storage class
|
65
|
+
if !storage_class.nil?
|
66
|
+
headers['x-amz-storage-class'] = storage_class.to_s.upcase
|
67
|
+
end
|
68
|
+
|
69
|
+
# Let's do it
|
70
|
+
response = bucket.connection.put(key, headers, value)
|
71
|
+
|
72
|
+
if response[:status] != 200
|
73
|
+
self.errors = response[:body]
|
74
|
+
else
|
75
|
+
self.errors = nil
|
76
|
+
end
|
77
|
+
|
78
|
+
response[:status] == 200
|
79
|
+
end
|
80
|
+
|
81
|
+
def delete
|
82
|
+
bucket.connection.delete(key)[:status] == 204
|
83
|
+
end
|
84
|
+
|
85
|
+
def value
|
86
|
+
fetch if !@value
|
87
|
+
@value
|
88
|
+
end
|
89
|
+
|
90
|
+
def persisted?
|
91
|
+
# TODO
|
92
|
+
end
|
93
|
+
|
94
|
+
def url
|
95
|
+
# TODO
|
96
|
+
end
|
97
|
+
|
98
|
+
def key=(key)
|
99
|
+
@key = key.gsub(/^\//,'')
|
100
|
+
end
|
101
|
+
|
102
|
+
##########################
|
103
|
+
|
104
|
+
def value=(value)
|
105
|
+
self.size = value.to_s.bytesize
|
106
|
+
@value = value
|
107
|
+
end
|
108
|
+
|
109
|
+
def access=(access)
|
110
|
+
valid_values = [:private, :public_read, :public_read_write, :authenticated_read]
|
111
|
+
|
112
|
+
if valid_values.include?(access)
|
113
|
+
@access = access
|
114
|
+
else
|
115
|
+
raise "Invalid access value"
|
116
|
+
end
|
117
|
+
end
|
118
|
+
|
119
|
+
def storage_class=(storage_class)
|
120
|
+
valid_values = [:standard, :reduced_redundancy]
|
121
|
+
|
122
|
+
if valid_values.include?(storage_class)
|
123
|
+
@storage_class = storage_class
|
124
|
+
else
|
125
|
+
raise "Invalid storage_class value"
|
126
|
+
end
|
127
|
+
end
|
128
|
+
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
class UberS3
|
2
|
+
module Operation
|
3
|
+
|
4
|
+
# Load and include all operations
|
5
|
+
Dir[File.dirname(__FILE__)+'/operation/*'].each do |group|
|
6
|
+
Dir[group+'/*.rb'].each do |op|
|
7
|
+
|
8
|
+
group_klass_name = File.basename(group).split('_').map {|x| x.capitalize}.join("")
|
9
|
+
op_klass_name = File.basename(op).gsub(/.rb$/i, '').split('_').map {|x| x.capitalize}.join("")
|
10
|
+
|
11
|
+
require op
|
12
|
+
op_klass = instance_eval(group_klass_name+'::'+op_klass_name)
|
13
|
+
group_klass = instance_eval(group_klass_name)
|
14
|
+
|
15
|
+
group_klass.send :include, op_klass
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module AccessPolicy
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
attr_accessor :access
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
module ClassMethods
|
14
|
+
end
|
15
|
+
|
16
|
+
module InstanceMethods
|
17
|
+
end
|
18
|
+
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module CacheControl
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module ContentDisposition
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module ContentEncoding
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module ContentMd5
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module ContentType
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module Expires
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module Meta
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module UberS3::Operation::Object
|
2
|
+
module StorageClass
|
3
|
+
|
4
|
+
def self.included(base)
|
5
|
+
base.send :extend, ClassMethods
|
6
|
+
base.send :include, InstanceMethods
|
7
|
+
|
8
|
+
base.instance_eval do
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module ClassMethods
|
13
|
+
end
|
14
|
+
|
15
|
+
module InstanceMethods
|
16
|
+
end
|
17
|
+
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
begin
|
2
|
+
require 'nokogiri'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rexml/document'
|
5
|
+
end
|
6
|
+
|
7
|
+
module UberS3::Util
|
8
|
+
class XmlDocument
|
9
|
+
|
10
|
+
def initialize(xml)
|
11
|
+
if defined?(Nokogiri)
|
12
|
+
@parser = NokogiriParser.new(xml)
|
13
|
+
else
|
14
|
+
@parser = RexmlParser.new(xml)
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
def method_missing(sym, *args, &block)
|
19
|
+
@parser.send sym, *args, &block
|
20
|
+
end
|
21
|
+
|
22
|
+
class RexmlParser
|
23
|
+
attr_accessor :doc
|
24
|
+
|
25
|
+
def initialize(xml)
|
26
|
+
self.doc = REXML::Document.new(xml)
|
27
|
+
end
|
28
|
+
|
29
|
+
def xpath(path)
|
30
|
+
REXML::XPath.match(doc.root, path)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
class NokogiriParser
|
35
|
+
attr_accessor :doc
|
36
|
+
|
37
|
+
def initialize(xml)
|
38
|
+
self.doc = Nokogiri::XML(xml)
|
39
|
+
doc.remove_namespaces!
|
40
|
+
end
|
41
|
+
|
42
|
+
def xpath(path)
|
43
|
+
doc.xpath(path).to_a
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
end
|
48
|
+
end
|
metadata
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: uber-s3
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease:
|
5
|
+
version: 0.1.1
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- Peter Kieltyka
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2011-06-01 00:00:00 -04:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: rake
|
18
|
+
prerelease: false
|
19
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
20
|
+
none: false
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: "0"
|
25
|
+
type: :development
|
26
|
+
version_requirements: *id001
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: rspec
|
29
|
+
prerelease: false
|
30
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
31
|
+
none: false
|
32
|
+
requirements:
|
33
|
+
- - ~>
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 2.6.0
|
36
|
+
type: :development
|
37
|
+
version_requirements: *id002
|
38
|
+
description: A simple & very fast S3 client supporting sync and async communication
|
39
|
+
email:
|
40
|
+
- peter@nulayer.com
|
41
|
+
executables: []
|
42
|
+
|
43
|
+
extensions: []
|
44
|
+
|
45
|
+
extra_rdoc_files: []
|
46
|
+
|
47
|
+
files:
|
48
|
+
- README.md
|
49
|
+
- lib/uber-s3/authorization.rb
|
50
|
+
- lib/uber-s3/bucket.rb
|
51
|
+
- lib/uber-s3/connection/em_http.rb
|
52
|
+
- lib/uber-s3/connection/em_http_fibered.rb
|
53
|
+
- lib/uber-s3/connection/net_http.rb
|
54
|
+
- lib/uber-s3/connection.rb
|
55
|
+
- lib/uber-s3/exceptions.rb
|
56
|
+
- lib/uber-s3/object.rb
|
57
|
+
- lib/uber-s3/operation/object/access_policy.rb
|
58
|
+
- lib/uber-s3/operation/object/cache_control.rb
|
59
|
+
- lib/uber-s3/operation/object/content_disposition.rb
|
60
|
+
- lib/uber-s3/operation/object/content_encoding.rb
|
61
|
+
- lib/uber-s3/operation/object/content_md5.rb
|
62
|
+
- lib/uber-s3/operation/object/content_type.rb
|
63
|
+
- lib/uber-s3/operation/object/expires.rb
|
64
|
+
- lib/uber-s3/operation/object/meta.rb
|
65
|
+
- lib/uber-s3/operation/object/storage_class.rb
|
66
|
+
- lib/uber-s3/operation.rb
|
67
|
+
- lib/uber-s3/util/xml_document.rb
|
68
|
+
- lib/uber-s3/version.rb
|
69
|
+
- lib/uber-s3.rb
|
70
|
+
has_rdoc: true
|
71
|
+
homepage: http://github.com/nulayer/uber-s3
|
72
|
+
licenses: []
|
73
|
+
|
74
|
+
post_install_message:
|
75
|
+
rdoc_options: []
|
76
|
+
|
77
|
+
require_paths:
|
78
|
+
- lib
|
79
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
80
|
+
none: false
|
81
|
+
requirements:
|
82
|
+
- - ">="
|
83
|
+
- !ruby/object:Gem::Version
|
84
|
+
version: "0"
|
85
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
86
|
+
none: false
|
87
|
+
requirements:
|
88
|
+
- - ">="
|
89
|
+
- !ruby/object:Gem::Version
|
90
|
+
version: 1.3.6
|
91
|
+
requirements: []
|
92
|
+
|
93
|
+
rubyforge_project:
|
94
|
+
rubygems_version: 1.6.2
|
95
|
+
signing_key:
|
96
|
+
specification_version: 3
|
97
|
+
summary: A simple & very fast S3 client supporting sync and async communication
|
98
|
+
test_files: []
|
99
|
+
|