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 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
+