uber-s3 0.1.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/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
|
+
|