s3lib 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|