s3 0.2.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/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,6 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.gem
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2009 Jakub Kuźma, Mirosław Boruta
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,48 @@
1
+ = Stree
2
+
3
+ * repository: http://github.com/qoobaa/stree
4
+ * issue tracker: http://github.com/qoobaa/stree/issues
5
+ * rdoc: http://qoobaa.github.com/stree
6
+
7
+ == Usage
8
+
9
+ Coming soon, see Rdoc documentation.
10
+
11
+ == Command tool usage
12
+
13
+ You have to pass access key id (-a) and secret access key (-s) to the
14
+ command line tool. Stree reads ACCESS_KEY_ID and SECRET_ACCESS_KEY
15
+ environment variables and uses them by default, so if you don't want
16
+ to pass them each time, export them (e.g. in .bashrc file).
17
+
18
+ * list buckets
19
+ stree bucket
20
+
21
+ * create bucket
22
+ stree bucket add name-of-bucket
23
+
24
+ * list objects in bucket
25
+ stree bucket show name-of-bucket
26
+
27
+ * destroy bucket
28
+ stree bucket remove name-of-bucket
29
+
30
+ * list objects (in all buckets)
31
+ stree object
32
+
33
+ * show the object information
34
+ stree object show bucket_name/path/to/object.extension
35
+
36
+ * download the content of the object
37
+ stree object show bucket_name/path/to/object.extension filename_to_store_the_content.extension
38
+
39
+ * show the content of the object to STDOUT
40
+ stree object show bucket_name/path/to/object.extension -
41
+
42
+ You can also pass --help to the commmands like:
43
+ stree bucket add --help
44
+ stree object show --help
45
+
46
+ == Copyright
47
+
48
+ Copyright (c) 2009 Jakub Kuźma, Mirosław Boruta. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,58 @@
1
+ # encoding: UTF-8
2
+
3
+ require 'rubygems'
4
+ require 'rake'
5
+
6
+ begin
7
+ require 'jeweler'
8
+ Jeweler::Tasks.new do |gem|
9
+ gem.name = "s3"
10
+ gem.summary = %Q{Library for accessing S3 objects and buckets, with command line tool}
11
+ gem.email = "qoobaa@gmail.com"
12
+ gem.homepage = "http://github.com/qoobaa/stree"
13
+ gem.authors = ["Jakub Kuźma", "Mirosław Boruta"]
14
+ gem.add_dependency "trollop", ">=1.14"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: sudo gem install jeweler"
20
+ end
21
+
22
+ require 'rake/testtask'
23
+ Rake::TestTask.new(:test) do |test|
24
+ test.libs << 'lib' << 'test'
25
+ test.pattern = 'test/**/*_test.rb'
26
+ test.verbose = true
27
+ end
28
+
29
+ begin
30
+ require 'rcov/rcovtask'
31
+ Rcov::RcovTask.new do |test|
32
+ test.libs << 'test'
33
+ test.pattern = 'test/**/*_test.rb'
34
+ test.verbose = true
35
+ end
36
+ rescue LoadError
37
+ task :rcov do
38
+ abort "RCov is not available. In order to run rcov, you must: sudo gem install spicycode-rcov"
39
+ end
40
+ end
41
+
42
+ task :default => :test
43
+
44
+ require 'rake/rdoctask'
45
+ Rake::RDocTask.new do |rdoc|
46
+ if File.exist?('VERSION.yml')
47
+ config = YAML.load(File.read('VERSION.yml'))
48
+ version = "#{config[:major]}.#{config[:minor]}.#{config[:patch]}"
49
+ else
50
+ version = ""
51
+ end
52
+
53
+ rdoc.rdoc_dir = 'rdoc'
54
+ rdoc.title = "stree #{version}"
55
+ rdoc.rdoc_files.include('README*')
56
+ rdoc.rdoc_files.include('lib/**/*.rb')
57
+ end
58
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.2.0
data/bin/stree ADDED
@@ -0,0 +1,187 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ $: << File.expand_path(File.dirname(__FILE__) + "/../lib")
4
+
5
+ require "trollop"
6
+ require "stree"
7
+
8
+ # HELPER METHODS
9
+
10
+ include Stree
11
+
12
+ def list_buckets(service)
13
+ service.buckets.each do |bucket|
14
+ puts bucket.name
15
+ end
16
+ end
17
+
18
+ def create_bucket(service, name, location)
19
+ service.buckets.build(name).save(location)
20
+ end
21
+
22
+ def destroy_bucket(service, name)
23
+ service.buckets.find(name).destroy
24
+ end
25
+
26
+ def show_bucket(service, name, options = {})
27
+ service.buckets.find(name).objects.find_all.each do |object|
28
+ puts "#{name}/#{object.key}"
29
+ end
30
+ end
31
+
32
+ def list_objects(service)
33
+ service.buckets.each do |bucket|
34
+ bucket.objects.each do |object|
35
+ puts "#{bucket.name}/#{object.key}"
36
+ end
37
+ end
38
+ end
39
+
40
+ def create_object(service, name, file_name, options = {})
41
+ bucket_name, object_name = name.split("/", 2)
42
+ object = service.buckets.find(bucket_name).objects.build(object_name)
43
+ object.content_type = options[:type]
44
+ object.content_encoding = options[:encoding]
45
+ object.content_disposition = options[:disposition]
46
+ object.acl = options[:acl]
47
+ object.content = File.new(file_name)
48
+ object.save
49
+ end
50
+
51
+ def destroy_object(service, name)
52
+ bucket_name, object_name = name.split("/", 2)
53
+ object = service.buckets.find(bucket_name).objects.find(object_name)
54
+ object.destroy
55
+ end
56
+
57
+ def show_object(service, name, file_name = nil)
58
+ bucket_name, object_name = name.split("/", 2)
59
+ object = service.buckets.find(bucket_name).objects.find_first(object_name)
60
+ puts " object: #{object.name}/#{object.key}"
61
+ puts " content type: #{object.content_type}"
62
+ puts " size: #{object.size}"
63
+ puts " etag: #{object.etag}"
64
+ puts " last modified: #{object.last_modified}"
65
+ if file_name
66
+ if file_name == "-"
67
+ puts object.content
68
+ else
69
+ File.open(file_name, "wb") do |file|
70
+ file.write(object.content)
71
+ end
72
+ end
73
+ end
74
+ end
75
+
76
+ # COMMAND LINE PARSER
77
+
78
+ ACCESS_KEY_ID = ENV["ACCESS_KEY_ID"]
79
+ SECRET_ACCESS_KEY = ENV["SECRET_ACCESS_KEY"]
80
+ COMMANDS = %w(bucket object)
81
+ BUCKET_SUBCOMMANDS = %w(add remove show)
82
+ OBJECT_SUBCOMMANDS = %w(add remove show)
83
+
84
+ global_options = Trollop::options do
85
+ banner "stree - s3 command line tool"
86
+ opt :access_key_id, "Your access key id to AWS", :type => :string, :default => ACCESS_KEY_ID
87
+ opt :secret_access_key, "Your secret access key to AWS", :type => :string, :default => SECRET_ACCESS_KEY
88
+ opt :debug, "Debug mode", :type => :flag, :default => false
89
+ stop_on COMMANDS
90
+ end
91
+
92
+ Trollop::die "No access key id given" unless global_options[:access_key_id]
93
+ Trollop::die "No secret access key given" unless global_options[:secret_access_key]
94
+
95
+ service = Service.new(:access_key_id => global_options[:access_key_id],
96
+ :secret_access_key => global_options[:secret_access_key],
97
+ :debug => global_options[:debug])
98
+
99
+ command = ARGV.shift
100
+
101
+ begin
102
+ case command
103
+ when "bucket"
104
+ command_options = Trollop::options do
105
+ banner "manage buckets"
106
+ stop_on BUCKET_SUBCOMMANDS
107
+ end
108
+ subcommand = ARGV.shift
109
+ case subcommand
110
+ when "add"
111
+ subcommand_options = Trollop::options do
112
+ banner "add bucket"
113
+ opt :location, "Location of the bucket - EU or US", :default => "US", :type => :string
114
+ end
115
+ name = ARGV.shift
116
+ Trollop::die "Bucket has not been added because of unknown error" unless create_bucket(service, name, subcommand_options[:location])
117
+ when "remove"
118
+ subcommand_options = Trollop::options do
119
+ banner "remove bucket"
120
+ end
121
+ name = ARGV.shift
122
+ Trollop::die "Bucket name must be given" if name.nil? or name.empty?
123
+ Trollop::die "Bucket has not been removed because of unknown error" unless destroy_bucket(service, name)
124
+ when "show"
125
+ subcommand_options = Trollop::options do
126
+ banner "show bucket"
127
+ opt :prefix, "Limits the response to keys which begin with the indicated prefix", :type => :string
128
+ opt :marker, "Indicates where in the bucket to begin listing", :type => :string
129
+ opt :max_keys, "The maximum number of keys you'd like to see", :type => :integer
130
+ opt :delimiter, "Causes keys that contain the same string between the prefix and the first occurrence of the delimiter to be rolled up into a single result element", :type => :string
131
+ end
132
+ name = ARGV.shift
133
+ Trollop::die "Bucket name must be given" if name.nil? or name.empty?
134
+ show_bucket(service, name, subcommand_options)
135
+ when nil
136
+ list_buckets(service)
137
+ else
138
+ Trollop::die "Unknown subcommand: #{subcommand.inspect}"
139
+ end
140
+ when "object"
141
+ command_options = Trollop::options do
142
+ banner "manage objects"
143
+ stop_on OBJECT_SUBCOMMANDS
144
+ end
145
+ subcommand = ARGV.shift
146
+ case subcommand
147
+ when "add"
148
+ subcommand_options = Trollop::options do
149
+ banner "object add s3_object_name local_file_name"
150
+ opt :type, "A standard MIME type describing the format of the contents", :default => "binary/octet-stream"
151
+ opt :disposition, "Specifies presentational information for the object", :type => :string
152
+ opt :encoding, "Specifies what content encodings have been applied to the object and thus what decoding mechanisms must be applied in order to obtain the media-type referenced by the Content-Type header field", :type => :string
153
+ opt :acl, "The canned ACL to apply to the object. Options include private, public-read, public-read-write, and authenticated-read", :type => :string
154
+ end
155
+ name = ARGV.shift
156
+ Trollop::die "No object name given" if name.nil? or name.empty?
157
+ file_name = ARGV.shift
158
+ Trollop::die "No file name given" if file_name.nil? or file_name.empty?
159
+ Trollop::die "Object has not been added because of unknown error" unless create_object(service, name, file_name, subcommand_options)
160
+ when "remove"
161
+ subcommand_options = Trollop::options do
162
+ banner "object remove s3_object_name"
163
+ end
164
+ name = ARGV.shift
165
+ Trollop::die "No object name given" if name.nil? or name.empty?
166
+ Trollop::die "Object has not been removed because of unknown error" unless destroy_object(service, name)
167
+ when "show"
168
+ subcommand_options = Trollop::options do
169
+ banner "object show s3_object_name optional_file_name"
170
+ end
171
+ name = ARGV.shift
172
+ Trollop::die "No object name given" if name.nil? or name.empty?
173
+ file_name = ARGV.shift
174
+ show_object(service, name, file_name)
175
+ when nil
176
+ list_objects(service)
177
+ else
178
+ Trollop::die "Unknown subcommand: #{subcommand.inspect}"
179
+ end
180
+ when nil
181
+ Trollop::die "No command given"
182
+ else
183
+ Trollop::die "Unknown command #{command.inspect}"
184
+ end
185
+ rescue Error::ResponseError => e
186
+ Trollop::die e.message.sub(/\.+\Z/, "")
187
+ end
@@ -0,0 +1,159 @@
1
+ require "singleton"
2
+ require "stree"
3
+
4
+ # Stree Backend for attachment-fu plugin. After installing attachment-fu
5
+ # plugin, copy the file to:
6
+ # +vendor/plugins/attachment-fu/lib/technoweenie/attachment_fu/backends+
7
+ #
8
+ # To configure StreeBackend create initializer file in your Rails
9
+ # application, e.g. +config/initializers/stree_backend.rb+.
10
+ #
11
+ # Technoweenie::AttachmentFu::Backends::StreeBackend.configuration do |config|
12
+ # config.access_key_id = "..." # your access key id
13
+ # config.secret_access_key = "..." # your secret access key
14
+ # config.bucket_name = "..." # default bucket name to store attachments
15
+ # config.use_ssl = false # pass true if you want to communicate via SSL
16
+ # end
17
+
18
+ module Technoweenie
19
+ module AttachmentFu
20
+ module Backends
21
+ module StreeBackend
22
+
23
+ # StreeBackend configuration class
24
+ class Configuration
25
+ include Singleton
26
+
27
+ ATTRIBUTES = [:access_key_id, :secret_access_key, :use_ssl, :bucket_name]
28
+
29
+ attr_accessor *ATTRIBUTES
30
+ end
31
+
32
+ # Method used to configure StreeBackend, see the example above
33
+ def self.configuration
34
+ if block_given?
35
+ yield Configuration.instance
36
+ end
37
+ Configuration.instance
38
+ end
39
+
40
+ # :nodoc:
41
+ def self.included(base)
42
+ include Stree
43
+
44
+ service = Service.new(:access_key_id => configuration.access_key_id,
45
+ :secret_access_key => configuration.secret_access_key,
46
+ :use_ssl => configuration.use_ssl)
47
+
48
+ bucket_name = base.attachment_options[:bucket_name] || configuration.bucket_name
49
+
50
+ base.cattr_accessor :bucket
51
+ base.bucket = service.buckets.build(bucket_name) # don't connect
52
+
53
+ base.before_update :rename_file
54
+ end
55
+
56
+ # The attachment ID used in the full path of a file
57
+ def attachment_path_id
58
+ ((respond_to?(:parent_id) && parent_id) || id).to_s
59
+ end
60
+
61
+ # The pseudo hierarchy containing the file relative to the bucket name
62
+ # Example: <tt>:table_name/:id</tt>
63
+ def base_path
64
+ [attachment_options[:path_prefix], attachment_path_id].join("/")
65
+ end
66
+
67
+ # The full path to the file relative to the bucket name
68
+ # Example: <tt>:table_name/:id/:filename</tt>
69
+ def full_filename(thumbnail = nil)
70
+ [base_path, thumbnail_name_for(thumbnail)].join("/")
71
+ end
72
+
73
+ # All public objects are accessible via a GET request to the S3 servers. You can generate a
74
+ # url for an object using the s3_url method.
75
+ #
76
+ # @photo.s3_url
77
+ #
78
+ # The resulting url is in the form: <tt>http(s)://:server/:bucket_name/:table_name/:id/:file</tt> where
79
+ # the <tt>:server</tt> variable defaults to <tt>AWS::S3 URL::DEFAULT_HOST</tt> (s3.amazonaws.com) and can be
80
+ # set using the configuration parameters in <tt>RAILS_ROOT/config/amazon_s3.yml</tt>.
81
+ #
82
+ # The optional thumbnail argument will output the thumbnail's filename (if any).
83
+ def s3_url(thumbnail = nil)
84
+ if attachment_options[:cname]
85
+ ["#{s3_protocol}#{bucket.name}", full_filename(thumbnail)].join("/")
86
+ else
87
+ ["#{s3_protocol}#{s3_hostname}#{bucket.path_prefix}", full_filename(thumbnail)].join("/")
88
+ end
89
+ end
90
+ alias :public_url :s3_url
91
+ alias :public_filename :s3_url
92
+
93
+ # Name of the bucket used to store attachments
94
+ def bucket_name
95
+ self.class.bucket.name
96
+ end
97
+
98
+ # :nodoc:
99
+ def create_temp_file
100
+ write_to_temp_file current_data
101
+ end
102
+
103
+ # :nodoc:
104
+ def current_data
105
+ # Object.value full_filename, bucket_name
106
+ object = self.class.bucket.objects.find(full_filename)
107
+ object.content
108
+ end
109
+
110
+ # Returns http:// or https:// depending on use_ssl setting
111
+ def s3_protocol
112
+ attachment_options[:use_ssl] ? "https://" : "http://"
113
+ end
114
+
115
+ # Returns hostname of the bucket
116
+ # e.g. +bucketname.com.s3.amazonaws.com+. Additionally you can
117
+ # pass :cname => true option in has_attachment method to
118
+ # return CNAME only, e.g. +bucketname.com+
119
+ def s3_hostname
120
+ attachment_options[:cname] ? self.class.bucket.name : self.class.bucket.host
121
+ end
122
+
123
+ protected
124
+
125
+ # Frees the space in S3 bucket, used by after_destroy callback
126
+ def destroy_file
127
+ object = self.class.bucket.objects.find(full_filename)
128
+ object.destroy
129
+ end
130
+
131
+ # Renames file if filename has been changed - copy the file to
132
+ # new key and delete old one
133
+ def rename_file
134
+ return unless filename_changed?
135
+
136
+ old_full_filename = [base_path, filename_was].join("/")
137
+
138
+ object = self.class.bucket.objects.find(old_full_filename)
139
+ new_object = object.copy(:key => full_filename, :acl => attachment_options[:acl])
140
+ object.destroy
141
+ true
142
+ end
143
+
144
+ # Saves the file to storage
145
+ def save_to_storage
146
+ if save_attachment?
147
+ object = self.class.bucket.objects.build(full_filename)
148
+
149
+ object.content_type = content_type
150
+ object.acl = attachment_options[:acl]
151
+ object.content = temp_path ? File.open(temp_path) : temp_data
152
+ object.save
153
+ end
154
+ true
155
+ end
156
+ end
157
+ end
158
+ end
159
+ end
@@ -0,0 +1,186 @@
1
+ module Stree
2
+ class Bucket
3
+ include Parser
4
+ extend Roxy::Moxie
5
+ extend Forwardable
6
+
7
+ attr_reader :name, :service
8
+
9
+ def_instance_delegators :service, :service_request
10
+ private_class_method :new
11
+
12
+ # Retrieves the bucket information from the server. Raises an
13
+ # Stree::Error exception if the bucket doesn't exist or you don't
14
+ # have access to it, etc.
15
+ def retrieve
16
+ list_bucket(:max_keys => 0)
17
+ self
18
+ end
19
+
20
+ # Returns location of the bucket, e.g. "EU"
21
+ def location(reload = false)
22
+ if reload or @location.nil?
23
+ @location = location_constraint
24
+ else
25
+ @location
26
+ end
27
+ end
28
+
29
+ # Compares the bucket with other bucket. Returns true if the key
30
+ # of the objects are the same, and both have the same buckets (see
31
+ # bucket equality)
32
+ def ==(other)
33
+ self.name == other.name and self.service == other.service
34
+ end
35
+
36
+ # Similar to retrieve, but catches NoSuchBucket exceptions and
37
+ # returns false instead.
38
+ def exists?
39
+ retrieve
40
+ true
41
+ rescue Error::NoSuchBucket
42
+ false
43
+ end
44
+
45
+ # Destroys given bucket. Raises an BucketNotEmpty exception if the
46
+ # bucket is not empty. You can destroy non-empty bucket passing
47
+ # true (to force destroy)
48
+ def destroy(force = false)
49
+ delete_bucket
50
+ true
51
+ rescue Error::BucketNotEmpty
52
+ if force
53
+ objects.destroy_all
54
+ retry
55
+ else
56
+ raise
57
+ end
58
+ end
59
+
60
+ # Saves the newly built bucket. Optionally you can pass location
61
+ # of the bucket (:eu or :us)
62
+ def save(location = nil)
63
+ create_bucket_configuration(location)
64
+ true
65
+ end
66
+
67
+ # Returns true if the name of the bucket can be used like VHOST
68
+ # name. If the bucket contains characters like underscore it can't
69
+ # be used as VHOST (e.g. bucket_name.s3.amazonaws.com)
70
+ def vhost?
71
+ "#@name.#{HOST}" =~ /\A#{URI::REGEXP::PATTERN::HOSTNAME}\Z/
72
+ end
73
+
74
+ # Returns host name of the bucket according (see vhost?)
75
+ def host
76
+ vhost? ? "#@name.#{HOST}" : "#{HOST}"
77
+ end
78
+
79
+ # Returns path prefix for non VHOST bucket. Path prefix is used
80
+ # instead of VHOST name,
81
+ # e.g. "bucket_name/"
82
+ def path_prefix
83
+ vhost? ? "" : "#@name/"
84
+ end
85
+
86
+ # Returns the objects in the bucket and caches the result (see
87
+ # reload).
88
+ def objects(reload = false)
89
+ if reload or @objects.nil?
90
+ @objects = list_bucket
91
+ else
92
+ @objects
93
+ end
94
+ end
95
+
96
+ proxy :objects do
97
+
98
+ # Builds the object in the bucket with given key
99
+ def build(key)
100
+ Object.send(:new, proxy_owner, :key => key)
101
+ end
102
+
103
+ # Finds first object with given name or raises the exception if
104
+ # not found
105
+ def find_first(name)
106
+ object = build(name)
107
+ object.retrieve
108
+ end
109
+ alias :find :find_first
110
+
111
+ # Finds the objects in the bucket.
112
+ # ==== Options:
113
+ # +prefix+:: Limits the response to keys which begin with the indicated prefix
114
+ # +marker+:: Indicates where in the bucket to begin listing
115
+ # +max_keys+:: The maximum number of keys you'd like to see
116
+ # +delimiter+:: Causes keys that contain the same string between the prefix and the first occurrence of the delimiter to be rolled up into a single result element
117
+ def find_all(options = {})
118
+ proxy_owner.send(:list_bucket, options)
119
+ end
120
+
121
+ # Reloads the object list (clears the cache)
122
+ def reload
123
+ proxy_owner.objects(true)
124
+ end
125
+
126
+ # Destroys all keys in the bucket
127
+ def destroy_all
128
+ proxy_target.each do |object|
129
+ object.destroy
130
+ end
131
+ end
132
+ end
133
+
134
+ def inspect #:nodoc:
135
+ "#<#{self.class}:#{name}>"
136
+ end
137
+
138
+ private
139
+
140
+ attr_writer :service
141
+
142
+ def location_constraint
143
+ response = bucket_request(:get, :params => { :location => nil })
144
+ parse_location_constraint(response.body)
145
+ end
146
+
147
+ def list_bucket(options = {})
148
+ response = bucket_request(:get, :params => options)
149
+ objects_attributes = parse_list_bucket_result(response.body)
150
+ objects_attributes.map { |object_attributes| Object.send(:new, self, object_attributes) }
151
+ end
152
+
153
+ def create_bucket_configuration(location = nil)
154
+ location = location.to_s.upcase if location
155
+ options = { :headers => {} }
156
+ if location and location != "US"
157
+ options[:body] = "<CreateBucketConfiguration><LocationConstraint>#{location}</LocationConstraint></CreateBucketConfiguration>"
158
+ options[:headers][:content_type] = "application/xml"
159
+ end
160
+ bucket_request(:put, options)
161
+ end
162
+
163
+ def delete_bucket
164
+ bucket_request(:delete)
165
+ end
166
+
167
+ def initialize(service, name) #:nodoc:
168
+ self.service = service
169
+ self.name = name
170
+ end
171
+
172
+ def name=(name)
173
+ raise ArgumentError.new("Invalid bucket name: #{name}") unless name_valid?(name)
174
+ @name = name
175
+ end
176
+
177
+ def bucket_request(method, options = {})
178
+ path = "#{path_prefix}#{options[:path]}"
179
+ service_request(method, options.merge(:host => host, :path => path))
180
+ end
181
+
182
+ def name_valid?(name)
183
+ name =~ /\A[a-z0-9][a-z0-9\._-]{2,254}\Z/ and name !~ /\A#{URI::REGEXP::PATTERN::IPV4ADDR}\Z/
184
+ end
185
+ end
186
+ end