s3 0.2.0

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