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 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,7 @@
1
+ class UberS3
2
+
3
+ class UberS3Error < StandardError; end
4
+
5
+
6
+
7
+ 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
@@ -0,0 +1,3 @@
1
+ class UberS3
2
+ VERSION = '0.1.1'
3
+ 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
+