s3lib 0.1.0
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/Rakefile +24 -0
- data/VERSION.yml +5 -0
- data/bin/s3lib +15 -0
- data/bin/s3sh_as +15 -0
- data/github-test.rb +22 -0
- data/lib/acl.rb +134 -0
- data/lib/acl_access.rb +20 -0
- data/lib/acl_creating_a_grant_recipe.rb +95 -0
- data/lib/acl_reading_acl_recipe.rb +59 -0
- data/lib/acl_refreshing_cached_grants_recipe.rb +54 -0
- data/lib/bucket.rb +116 -0
- data/lib/bucket_before_refactoring.rb +120 -0
- data/lib/bucket_create.rb +39 -0
- data/lib/bucket_find.rb +41 -0
- data/lib/bucket_with_acl_mixin.rb +103 -0
- data/lib/error_handling.rb +12 -0
- data/lib/grant.rb +107 -0
- data/lib/grant_creating_a_grant_recipe.rb +103 -0
- data/lib/grant_reading_acl_recipe.rb +51 -0
- data/lib/object.rb +144 -0
- data/lib/object_from_bucket_test.rb +18 -0
- data/lib/object_take1.rb +150 -0
- data/lib/object_with_acl_mixin.rb +131 -0
- data/lib/put_with_curl_test.rb +39 -0
- data/lib/s3_authenticator.rb +155 -0
- data/lib/s3_authenticator_dev.rb +117 -0
- data/lib/s3_authenticator_dev_private.rb +40 -0
- data/lib/s3_errors.rb +58 -0
- data/lib/s3lib.rb +10 -0
- data/lib/s3lib_with_mixin.rb +11 -0
- data/lib/service.rb +24 -0
- data/lib/service_dev.rb +36 -0
- data/s3lib.gemspec +74 -0
- data/sample_usage.rb +45 -0
- data/test/acl_test.rb +89 -0
- data/test/amazon_headers_test.rb +87 -0
- data/test/canonical_resource_test.rb +53 -0
- data/test/canonical_string_tests.rb +73 -0
- data/test/first_test.rb +34 -0
- data/test/first_test_private.rb +55 -0
- data/test/full_test.rb +84 -0
- data/test/s3_authenticator_test.rb +291 -0
- metadata +109 -0
@@ -0,0 +1,39 @@
|
|
1
|
+
# s3_bucket.rb
|
2
|
+
require File.join(File.dirname(__FILE__), 's3_authenticator')
|
3
|
+
module S3Lib
|
4
|
+
|
5
|
+
class NotYourBucketError < S3Lib::S3ResponseError
|
6
|
+
end
|
7
|
+
|
8
|
+
class Bucket
|
9
|
+
|
10
|
+
# Todo:
|
11
|
+
# Class methods
|
12
|
+
# Bucket::find
|
13
|
+
# Bucket::delete (have :force => true)
|
14
|
+
# Bucket::delete_all_objects
|
15
|
+
# Bucket::objects
|
16
|
+
# Bucket::new
|
17
|
+
# instance methods
|
18
|
+
# Bucket#objects
|
19
|
+
# Bucket#delete
|
20
|
+
# Bucket#delete_all_objects
|
21
|
+
# Bucket#each
|
22
|
+
|
23
|
+
def self.create(name, params = {})
|
24
|
+
params['x-amz-acl'] = params.delete(:access) if params[:access] # translate from :access to 'x-amz-acl'
|
25
|
+
begin
|
26
|
+
response = S3Lib.request(:put, name, params)
|
27
|
+
rescue S3Lib::S3ResponseError => error
|
28
|
+
if error.amazon_error_type == "BucketAlreadyExists"
|
29
|
+
raise S3Lib::NotYourBucketError.new("The bucket '#{name}' is already owned by somebody else", error.io, error.s3requester)
|
30
|
+
else
|
31
|
+
raise # re-raise the exception if it's not a BucketAlreadyExists error
|
32
|
+
end
|
33
|
+
end
|
34
|
+
response.status[0] == "200" ? true : false
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
data/lib/bucket_find.rb
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
# s3_bucket.rb
|
2
|
+
require File.join(File.dirname(__FILE__), 's3_authenticator')
|
3
|
+
require 'rexml/document'
|
4
|
+
|
5
|
+
module S3Lib
|
6
|
+
|
7
|
+
class Bucket
|
8
|
+
|
9
|
+
attr_reader :xml, :prefix, :marker, :max_keys
|
10
|
+
|
11
|
+
def self.find(name, params = {})
|
12
|
+
response = S3Lib.request(:get, name)
|
13
|
+
doc = REXML::Document.new(response)
|
14
|
+
Bucket.new(doc)
|
15
|
+
end
|
16
|
+
|
17
|
+
def initialize(doc)
|
18
|
+
@xml = doc.root
|
19
|
+
@name = @xml.elements['Name'].text
|
20
|
+
@max_keys = @xml.elements['MaxKeys'].text.to_i
|
21
|
+
@prefix = @xml.elements['Prefix'].text
|
22
|
+
@marker = @xml.elements['Marker'].text
|
23
|
+
end
|
24
|
+
|
25
|
+
def is_truncated?
|
26
|
+
@xml.elements['IsTruncated'].text == 'true'
|
27
|
+
end
|
28
|
+
|
29
|
+
def objects
|
30
|
+
REXML::XPath.match(@xml, '//Contents').collect do |object|
|
31
|
+
S3Lib::S3Object.new(object)
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
if __FILE__ == $0
|
40
|
+
S3Lib::Bucket.find('spatten_syncdemo')
|
41
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# bucket.rb
|
2
|
+
module S3Lib
|
3
|
+
|
4
|
+
class Bucket
|
5
|
+
|
6
|
+
attr_reader :name, :xml, :prefix, :marker, :max_keys
|
7
|
+
|
8
|
+
include S3Lib::AclAccess
|
9
|
+
|
10
|
+
def self.create(name, params = {})
|
11
|
+
params['x-amz-acl'] = params.delete(:access) if params[:access] # translate from :access to 'x-amz-acl'
|
12
|
+
response = self.bucket_request(:put, name, params)
|
13
|
+
response.status[0] == "200" ? true : false
|
14
|
+
end
|
15
|
+
|
16
|
+
# passing :force => true will cause the bucket to be deleted even if it is not empty.
|
17
|
+
def self.delete(name, params = {})
|
18
|
+
if params.delete(:force)
|
19
|
+
self.delete_all(name, params)
|
20
|
+
end
|
21
|
+
response = self.bucket_request(:delete, name, params)
|
22
|
+
end
|
23
|
+
|
24
|
+
def delete(params = {})
|
25
|
+
self.class.delete(@name, @params.merge(params))
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.delete_all(name, params = {})
|
29
|
+
bucket = Bucket.find(name, params)
|
30
|
+
bucket.delete_all
|
31
|
+
end
|
32
|
+
|
33
|
+
def delete_all
|
34
|
+
objects.each do |object|
|
35
|
+
object.delete
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.find(name, params = {})
|
40
|
+
response = self.bucket_request(:get, name, params)
|
41
|
+
doc = REXML::Document.new(response)
|
42
|
+
Bucket.new(doc, params)
|
43
|
+
end
|
44
|
+
|
45
|
+
def initialize(doc, params = {})
|
46
|
+
@xml = doc.root
|
47
|
+
@params = params
|
48
|
+
@name = @xml.elements['Name'].text
|
49
|
+
@max_keys = @xml.elements['MaxKeys'].text.to_i
|
50
|
+
@prefix = @xml.elements['Prefix'].text
|
51
|
+
@marker = @xml.elements['Marker'].text
|
52
|
+
end
|
53
|
+
|
54
|
+
def is_truncated?
|
55
|
+
@xml.elements['IsTruncated'].text == 'true'
|
56
|
+
end
|
57
|
+
|
58
|
+
def objects(params = {})
|
59
|
+
refresh if params[:refresh]
|
60
|
+
@objects || get_objects
|
61
|
+
end
|
62
|
+
|
63
|
+
def refresh
|
64
|
+
refreshed_bucket = Bucket.find(@name, @params)
|
65
|
+
@xml = refreshed_bucket.xml
|
66
|
+
@objects = nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def url
|
70
|
+
@name
|
71
|
+
end
|
72
|
+
|
73
|
+
# access an object in the bucket by key name
|
74
|
+
def [](key)
|
75
|
+
objects.detect{|object| object.key == key}
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def self.bucket_request(verb, name, params = {})
|
81
|
+
begin
|
82
|
+
response = S3Lib.request(verb, name, params)
|
83
|
+
rescue S3Lib::S3ResponseError => error
|
84
|
+
case error.amazon_error_type
|
85
|
+
when "NoSuchBucket": raise S3Lib::BucketNotFoundError.new("The bucket '#{name}' does not exist.", error.io, error.s3requester)
|
86
|
+
when "NotSignedUp": raise S3Lib::NotYourBucketError.new("The bucket '#{name}' is owned by someone else.", error.io, error.s3requester)
|
87
|
+
when "BucketNotEmpty": raise S3Lib::BucketNotEmptyError.new("The bucket '#{name}' is not empty, so you can't delete it.\nTry using Bucket.delete_all('#{name}') first, or Bucket.delete('#{name}', :force => true).", error.io, error.s3requester)
|
88
|
+
else # Re-raise the error if it's not one of the above
|
89
|
+
raise
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_objects
|
95
|
+
@objects = REXML::XPath.match(@xml, '//Contents').collect do |object|
|
96
|
+
key = object.elements['Key'].text
|
97
|
+
S3Lib::S3Object.new(self, key, :lazy_load => true)
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
@@ -0,0 +1,12 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__),'s3_authenticator')
|
4
|
+
|
5
|
+
begin
|
6
|
+
req = S3Lib.request(:put, "spatten_sample_bucket/sample_object", :body => "Wheee")
|
7
|
+
rescue S3Lib::S3ResponseError => e
|
8
|
+
puts "Amazon Error Type: #{e.amazon_error_type}"
|
9
|
+
puts "HTTP Status: #{e.status.join(',')}"
|
10
|
+
puts "Response from Amazon: #{e.response}"
|
11
|
+
puts "canonical string: #{e.s3requester.canonical_string}" if e.amazon_error_type == 'SignatureDoesNotMatch'
|
12
|
+
end
|
data/lib/grant.rb
ADDED
@@ -0,0 +1,107 @@
|
|
1
|
+
module S3Lib
|
2
|
+
class Grant
|
3
|
+
attr_reader :acl, :grantee, :type, :permission
|
4
|
+
GRANT_TYPES = {:canonical => 'CanonicalUser',
|
5
|
+
:email => 'AmazonCustomerByEmail',
|
6
|
+
:all_s3 => 'Group',
|
7
|
+
:public => 'Group'}
|
8
|
+
GROUP_URIS = {'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' => :all_s3,
|
9
|
+
'http://acs.amazonaws.com/groups/global/AllUsers' => :public}
|
10
|
+
PERMISSIONS = [:read, :write, :read_acl, :write_acl, :full_control]
|
11
|
+
NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema-instance'
|
12
|
+
|
13
|
+
# Create a new grant.
|
14
|
+
# permission is one of the PERMISSIONS defined above
|
15
|
+
# grantee can be either a REXML::Document object or a Hash
|
16
|
+
# The grantee Hash should look like this:
|
17
|
+
# {:type => :canonical|:email|:all_s3|:public,
|
18
|
+
# :grantee => canonical_user_id | email_address}
|
19
|
+
#
|
20
|
+
# The :grantee element of the hash is only required (and meaningful)
|
21
|
+
# for :canonical and :email Grants
|
22
|
+
def initialize(permission, grantee)
|
23
|
+
@type = parse_type(grantee)
|
24
|
+
@permission = parse_permission(permission)
|
25
|
+
@grantee = parse_grantee(grantee)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_xml
|
29
|
+
builder = Builder::XmlMarkup.new(:indent => 2)
|
30
|
+
xml = builder.Grant do
|
31
|
+
builder.Grantee('xmlns:xsi' => NAMESPACE_URI, 'xsi:type' => GRANT_TYPES[@type]) do
|
32
|
+
case type
|
33
|
+
when :canonical: builder.ID(@grantee)
|
34
|
+
when :email: builder.EmailAddress(@grantee)
|
35
|
+
when :all_s3: builder.URI(group_uri_from_group_type(:all_s3))
|
36
|
+
when :public: builder.URI(group_uri_from_group_type(:public))
|
37
|
+
else
|
38
|
+
end
|
39
|
+
end
|
40
|
+
builder.Permission(@permission.to_s.upcase)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def inspect
|
45
|
+
"#{@permission} to #{@type}#{" #{@grantee}" if [:email, :canonical].include?(@type)}"
|
46
|
+
end
|
47
|
+
|
48
|
+
private
|
49
|
+
|
50
|
+
# permission can either be the String provided by S3
|
51
|
+
# or a symbol (see the PERMISSIONS array for allowed values)
|
52
|
+
def parse_permission(permission)
|
53
|
+
if permission.is_a?(String)
|
54
|
+
permission.downcase.to_sym
|
55
|
+
else
|
56
|
+
permission
|
57
|
+
end
|
58
|
+
end
|
59
|
+
|
60
|
+
def parse_type(grantee)
|
61
|
+
if grantee.is_a?(Hash)
|
62
|
+
grantee[:type]
|
63
|
+
else # Assume it's a REXML::Doc object
|
64
|
+
type = grantee.attributes['xsi:type']
|
65
|
+
case type
|
66
|
+
when 'CanonicalUser': :canonical
|
67
|
+
when 'AmazonCustomerByEmail': :email
|
68
|
+
when 'Group'
|
69
|
+
group_uri = grantee.elements['URI'].text
|
70
|
+
group_type_from_group_uri(group_uri)
|
71
|
+
else
|
72
|
+
raise BadGrantTypeError
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
def parse_grantee(grantee)
|
78
|
+
if grantee.is_a?(Hash)
|
79
|
+
if [:canonical, :email].include?(@type)
|
80
|
+
grantee[:grantee]
|
81
|
+
else
|
82
|
+
@type
|
83
|
+
end
|
84
|
+
else # it's a REXML::Doc object
|
85
|
+
case @type
|
86
|
+
when :canonical
|
87
|
+
grantee.elements['ID'].text
|
88
|
+
when :email
|
89
|
+
grantee.elements['EmailAddress'].text
|
90
|
+
when :all_s3: :all_s3
|
91
|
+
when :public: :public
|
92
|
+
else
|
93
|
+
nil
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def group_type_from_group_uri(group_uri)
|
99
|
+
GROUP_URIS[group_uri]
|
100
|
+
end
|
101
|
+
|
102
|
+
def group_uri_from_group_type(group_type)
|
103
|
+
GROUP_URIS.invert[group_type]
|
104
|
+
end
|
105
|
+
|
106
|
+
end
|
107
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module S3Lib
|
2
|
+
class Grant
|
3
|
+
attr_reader :acl, :grantee, :type, :permission
|
4
|
+
GRANT_TYPES = {:canonical => 'CanonicalUser',
|
5
|
+
:email => 'AmazonCustomerByEmail',
|
6
|
+
:all_s3 => 'Group',
|
7
|
+
:public => 'Group'}
|
8
|
+
GROUP_URIS = {'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' => :all_s3,
|
9
|
+
'http://acs.amazonaws.com/groups/global/AllUsers' => :public}
|
10
|
+
PERMISSIONS = [:read, :write, :read_acl, :write_acl, :full_control]
|
11
|
+
NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema-instance'
|
12
|
+
|
13
|
+
# Create a new grant.
|
14
|
+
# permission is one of the PERMISSIONS defined above
|
15
|
+
# grantee can be either a REXML::Document object or a Hash
|
16
|
+
# The grantee Hash should look like this:
|
17
|
+
# {:type => :canonical|:email|:all_s3|:public,
|
18
|
+
# :grantee => canonical_user_id | email_address}
|
19
|
+
#
|
20
|
+
# The :grantee element of the hash is only required (and meaningful)
|
21
|
+
# for :canonical and :email Grants
|
22
|
+
def initialize(permission, grantee)
|
23
|
+
@type = parse_type(grantee)
|
24
|
+
@permission = parse_permission(permission)
|
25
|
+
@grantee = parse_grantee(grantee)
|
26
|
+
end
|
27
|
+
|
28
|
+
def to_xml
|
29
|
+
builder = Builder::XmlMarkup.new(:indent => 2)
|
30
|
+
xml = builder.Grant do
|
31
|
+
builder.Grantee('xmlns:xsi' => NAMESPACE_URI, 'xsi:type' => GRANT_TYPES[@type]) do
|
32
|
+
case type
|
33
|
+
when :canonical: builder.ID(@grantee)
|
34
|
+
when :email: builder.EmailAddress(@grantee)
|
35
|
+
when :all_s3: builder.URI(group_uri_from_group_type(:all_s3))
|
36
|
+
when :public: builder.URI(group_uri_from_group_type(:public))
|
37
|
+
else
|
38
|
+
end
|
39
|
+
end
|
40
|
+
builder.Permission(@permission.to_s.upcase)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# permission can either be the String provided by S3
|
47
|
+
# or a symbol (see the PERMISSIONS array for allowed values)
|
48
|
+
def parse_permission(permission)
|
49
|
+
if permission.is_a?(String)
|
50
|
+
permission.downcase.to_sym
|
51
|
+
else
|
52
|
+
permission
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def parse_type(grantee)
|
57
|
+
if grantee.is_a?(Hash)
|
58
|
+
grantee[:type]
|
59
|
+
else # Assume it's a REXML::Doc object
|
60
|
+
type = grantee.attributes['xsi:type']
|
61
|
+
case type
|
62
|
+
when 'CanonicalUser': :canonical
|
63
|
+
when 'AmazonCustomerByEmail': :email
|
64
|
+
when 'Group'
|
65
|
+
group_uri = grantee.elements['URI'].text
|
66
|
+
group_type_from_group_uri(group_uri)
|
67
|
+
else
|
68
|
+
raise BadGrantTypeError
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def parse_grantee(grantee)
|
74
|
+
if grantee.is_a?(Hash)
|
75
|
+
if [:canonical, :email].include?(@type)
|
76
|
+
grantee[:grantee]
|
77
|
+
else
|
78
|
+
@type
|
79
|
+
end
|
80
|
+
else # it's a REXML::Doc object
|
81
|
+
case @type
|
82
|
+
when :canonical
|
83
|
+
grantee.elements['ID'].text
|
84
|
+
when :email
|
85
|
+
grantee.elements['EmailAddress'].text
|
86
|
+
when :all_s3: :all_s3
|
87
|
+
when :public: :public
|
88
|
+
else
|
89
|
+
nil
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
def group_type_from_group_uri(group_uri)
|
95
|
+
GROUP_URIS[group_uri]
|
96
|
+
end
|
97
|
+
|
98
|
+
def group_uri_from_group_type(group_type)
|
99
|
+
GROUP_URIS.invert[group_type]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,51 @@
|
|
1
|
+
module S3Lib
|
2
|
+
class Grant
|
3
|
+
attr_reader :acl, :grantee, :type, :permission
|
4
|
+
GRANT_TYPES = {:canonical => 'CanonicalUser',
|
5
|
+
:email => 'AmazonCustomerByEmail',
|
6
|
+
:all_s3 => 'Group',
|
7
|
+
:public => 'Group'}
|
8
|
+
GROUP_URIS = {'http://acs.amazonaws.com/groups/global/AuthenticatedUsers' => :all_s3,
|
9
|
+
'http://acs.amazonaws.com/groups/global/AllUsers' => :public}
|
10
|
+
PERMISSIONS = [:read, :write, :read_acl, :write_acl, :full_control]
|
11
|
+
NAMESPACE_URI = 'http://www.w3.org/2001/XMLSchema-instance'
|
12
|
+
|
13
|
+
# Create a new grant.
|
14
|
+
# permission is one of the PERMISSIONS defined above
|
15
|
+
# grantee is the REXML::Document Grantee object returned by S3
|
16
|
+
def initialize(permission, grantee)
|
17
|
+
@permission = permission.downcase.to_sym
|
18
|
+
@type = parse_type(grantee)
|
19
|
+
@grantee = parse_grantee(grantee)
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def parse_type(grantee)
|
25
|
+
type = grantee.attributes['xsi:type']
|
26
|
+
case type
|
27
|
+
when 'CanonicalUser': :canonical
|
28
|
+
when 'AmazonCustomerByEmail': :email
|
29
|
+
when 'Group'
|
30
|
+
group_uri = grantee.elements['URI'].text
|
31
|
+
GROUP_URIS[group_uri]
|
32
|
+
else
|
33
|
+
raise BadGrantTypeError
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def parse_grantee(grantee)
|
38
|
+
case @type
|
39
|
+
when :canonical
|
40
|
+
grantee.elements['ID'].text
|
41
|
+
when :email
|
42
|
+
grantee.elements['EmailAddress'].text
|
43
|
+
when :all_s3: :all_s3
|
44
|
+
when :public: :public
|
45
|
+
else
|
46
|
+
nil
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
end
|
51
|
+
end
|
data/lib/object.rb
ADDED
@@ -0,0 +1,144 @@
|
|
1
|
+
# s3_object.rb
|
2
|
+
|
3
|
+
module S3Lib
|
4
|
+
|
5
|
+
class S3Object
|
6
|
+
|
7
|
+
DEFAULT_CONTENT_TYPE = 'binary/octect-stream'
|
8
|
+
|
9
|
+
attr_reader :key, :bucket
|
10
|
+
|
11
|
+
# This is just an alias for S3Object.new
|
12
|
+
def self.find(bucket, key, options = {})
|
13
|
+
S3Object.new(bucket, key, options)
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.create(bucket, key, value = "", options = {})
|
17
|
+
# translate from :access to 'x-amz-acl'
|
18
|
+
options['x-amz-acl'] = options.delete(:access) if options[:access]
|
19
|
+
options['content-type'] ||= DEFAULT_CONTENT_TYPE
|
20
|
+
options[:body] = value || ""
|
21
|
+
|
22
|
+
response = S3Object.object_request(:put, S3Object.url(bucket, key), options)
|
23
|
+
response.status[0] == "200" ? S3Object.new(bucket, key, options) : false
|
24
|
+
end
|
25
|
+
|
26
|
+
# Delete an object given the object's bucket and key.
|
27
|
+
# No error will be raised if the object does not exist.
|
28
|
+
def self.delete(bucket, key, options = {})
|
29
|
+
S3Object.object_request(:delete, S3Object.url(bucket, key), options)
|
30
|
+
end
|
31
|
+
|
32
|
+
def delete
|
33
|
+
S3Object.delete(@bucket, @key, @options)
|
34
|
+
end
|
35
|
+
|
36
|
+
def self.value(bucket, key, options = {})
|
37
|
+
request = S3Object.object_request(:get, S3Object.url(bucket, key), options)
|
38
|
+
request.read
|
39
|
+
end
|
40
|
+
|
41
|
+
# Both metadata and value are loaded lazily if options[:lazy_load] is true
|
42
|
+
# This is used by Bucket.find so you don't make a request for every object in the bucket
|
43
|
+
# The bucket can be either a bucket object or a string containing the bucket's name
|
44
|
+
# The key is a string.
|
45
|
+
def initialize(bucket, key, options = {})
|
46
|
+
bucket = Bucket.find(bucket) unless bucket.respond_to?(:name)
|
47
|
+
@bucket = bucket
|
48
|
+
@key = key
|
49
|
+
@options = options
|
50
|
+
get_metadata unless options[:lazy_load]
|
51
|
+
end
|
52
|
+
|
53
|
+
# bucket can be either a Bucket object or a string containing the bucket's name
|
54
|
+
def self.url(bucket, key)
|
55
|
+
bucket_name = bucket.respond_to?(:name) ? bucket.name : bucket
|
56
|
+
File.join(bucket_name, key)
|
57
|
+
end
|
58
|
+
|
59
|
+
def url
|
60
|
+
S3Object.url(@bucket.name, @key)
|
61
|
+
end
|
62
|
+
|
63
|
+
def metadata
|
64
|
+
@metadata || get_metadata
|
65
|
+
end
|
66
|
+
|
67
|
+
def value(params = {})
|
68
|
+
refresh if params[:refresh]
|
69
|
+
@value || get_value
|
70
|
+
end
|
71
|
+
|
72
|
+
def value=(value)
|
73
|
+
S3Object.create(@bucket, @key, value, @options)
|
74
|
+
@value = value
|
75
|
+
refresh_metadata
|
76
|
+
end
|
77
|
+
|
78
|
+
def refresh
|
79
|
+
get_value
|
80
|
+
end
|
81
|
+
|
82
|
+
def refresh_metadata
|
83
|
+
get_metadata
|
84
|
+
end
|
85
|
+
|
86
|
+
def refresh_acl
|
87
|
+
get_acl
|
88
|
+
end
|
89
|
+
|
90
|
+
def content_type
|
91
|
+
metadata["content-type"]
|
92
|
+
end
|
93
|
+
|
94
|
+
# strip off the leading and trailing double-quotes
|
95
|
+
def etag
|
96
|
+
metadata["etag"].sub(/\A\"/,'').sub(/\"\Z/, '')
|
97
|
+
end
|
98
|
+
|
99
|
+
def length
|
100
|
+
metadata["content-length"].to_i
|
101
|
+
end
|
102
|
+
|
103
|
+
def acl(params = {})
|
104
|
+
refresh_acl if params[:refresh]
|
105
|
+
@acl || get_acl
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
def self.object_request(verb, url, options = {})
|
111
|
+
begin
|
112
|
+
options.delete(:lazy_load)
|
113
|
+
response = S3Lib.request(verb, url, options)
|
114
|
+
rescue S3Lib::S3ResponseError => error
|
115
|
+
case error.amazon_error_type
|
116
|
+
when 'NoSuchBucket': raise S3Lib::BucketNotFoundError.new("The bucket '#{bucket}' does not exist.", error.io, error.s3requester)
|
117
|
+
when 'NotSignedUp': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by somebody else", error.io, error.s3requester)
|
118
|
+
when 'AccessDenied': raise S3Lib::NotYourBucketError.new("The bucket '#{bucket}' is owned by someone else.", error.io, error.s3requester)
|
119
|
+
when 'MissingContentLength': raise S3Lib::NoContentError.new("You must provide a value to put in the object.\nUsage: S3Lib::S3Object.create(bucket, key, value, options)", error.io, error.s3requester)
|
120
|
+
else # Re-raise the error if it's not one of the above
|
121
|
+
raise
|
122
|
+
end
|
123
|
+
end
|
124
|
+
response
|
125
|
+
end
|
126
|
+
|
127
|
+
def get_metadata
|
128
|
+
request = S3Object.object_request(:head, url, @options)
|
129
|
+
@metadata = request.meta
|
130
|
+
end
|
131
|
+
|
132
|
+
def get_value
|
133
|
+
request = S3Object.object_request(:get, url, @options)
|
134
|
+
@metadata = request.meta
|
135
|
+
@value = request.read
|
136
|
+
end
|
137
|
+
|
138
|
+
def get_acl
|
139
|
+
@acl = Acl.new(self)
|
140
|
+
end
|
141
|
+
|
142
|
+
end
|
143
|
+
|
144
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
require File.join(File.dirname(__FILE__), 'object')
|
4
|
+
require 'rexml/document'
|
5
|
+
require 'pp'
|
6
|
+
|
7
|
+
bucket_request = S3Lib.request(:get, 'spatten_syncdemo')
|
8
|
+
bucket_doc = REXML::Document.new(bucket_request.read)
|
9
|
+
first_object = REXML::XPath.match(bucket_doc, "//Contents").first
|
10
|
+
|
11
|
+
# puts first_object
|
12
|
+
|
13
|
+
puts "from bucket xml:"
|
14
|
+
pp first_object.to_hash
|
15
|
+
|
16
|
+
puts "from get on object:"
|
17
|
+
object = S3Lib.request(:get, 'spatten_syncdemo/flowers.jpg')
|
18
|
+
pp object.meta
|