steamcannon-s3 0.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,7 @@
1
+ *.sw?
2
+ .DS_Store
3
+ coverage
4
+ rdoc
5
+ pkg
6
+ *.gem
7
+ .bundle
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source :gemcutter
2
+
3
+ # Specify your gem's dependencies in s3.gemspec
4
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,27 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ s3 (0.3.2)
5
+ proxies
6
+ trollop
7
+
8
+ GEM
9
+ remote: http://rubygems.org/
10
+ specs:
11
+ mocha (0.9.8)
12
+ rake
13
+ proxies (0.1.1)
14
+ rake (0.8.7)
15
+ test-unit (2.1.1)
16
+ trollop (1.16.2)
17
+
18
+ PLATFORMS
19
+ ruby
20
+
21
+ DEPENDENCIES
22
+ bundler (>= 1.0.0)
23
+ mocha
24
+ proxies
25
+ s3!
26
+ test-unit (>= 2.0)
27
+ trollop
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,22 @@
1
+ = S3
2
+
3
+ S3 library provides access to {Amazon's Simple Storage Service}[http://aws.amazon.com/s3/].
4
+ It supports both: European and US buckets through {REST API}[http://docs.amazonwebservices.com/AmazonS3/latest/RESTAPI.html].
5
+
6
+ * homepage[http://jah.pl/projects/s3.html]
7
+ * gemcutter[http://gemcutter.org/gems/s3]
8
+ * repository[http://github.com/qoobaa/s3]
9
+ * {issue tracker}[http://github.com/qoobaa/s3/issues]
10
+ * rdoc[http://qoobaa.github.com/s3]
11
+
12
+ == Installation
13
+
14
+ gem install s3
15
+
16
+ == Usage
17
+
18
+ See homepage[http://jah.pl/projects/s3.html] for details.
19
+
20
+ == Copyright
21
+
22
+ Copyright (c) 2009 Jakub Kuźma, Mirosław Boruta. See LICENSE[http://github.com/qoobaa/s3/raw/master/LICENSE] for details.
data/Rakefile ADDED
@@ -0,0 +1,20 @@
1
+ require "bundler"
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require "rake/testtask"
5
+ require "rake/rdoctask"
6
+
7
+ Rake::TestTask.new(:test) do |test|
8
+ test.libs << "lib" << "test"
9
+ test.pattern = "test/**/*_test.rb"
10
+ test.verbose = true
11
+ end
12
+
13
+ Rake::RDocTask.new do |rdoc|
14
+ rdoc.rdoc_dir = "rdoc"
15
+ rdoc.title = "s3 #{S3::VERSION}"
16
+ rdoc.rdoc_files.include("README.rdoc")
17
+ rdoc.rdoc_files.include("lib/**/*.rb")
18
+ end
19
+
20
+ task :default => :test
data/bin/s3 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 "s3"
7
+
8
+ # HELPER METHODS
9
+
10
+ include S3
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 "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 "s3"
3
+
4
+ # S3 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 S3Backend create initializer file in your Rails
9
+ # application, e.g. +config/initializers/s3_backend.rb+.
10
+ #
11
+ # Technoweenie::AttachmentFu::Backends::S3Backend.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 S3Backend
22
+
23
+ # S3Backend 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 S3Backend, 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 S3
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,156 @@
1
+ # S3 backend for paperclip plugin. Copy the file to:
2
+ # +config/initializers/+ directory
3
+ #
4
+ # Example configuration for CNAME bucket:
5
+ #
6
+ # has_attached_file :image,
7
+ # :s3_host_alias => "bucket.domain.tld",
8
+ # :url => ":s3_alias_url",
9
+ # :styles => {
10
+ # :medium => "300x300>",
11
+ # :thumb => "100x100>"
12
+ # },
13
+ # :storage => :s3,
14
+ # :s3_credentials => {
15
+ # :access_key_id => "...",
16
+ # :secret_access_key => "..."
17
+ # },
18
+ # :bucket => "bucket.domain.tld",
19
+ # :path => ":attachment/:id/:style.:extension"
20
+ module Paperclip
21
+ module Storage
22
+ module S3
23
+ def self.extended base
24
+ begin
25
+ require "s3"
26
+ rescue LoadError => e
27
+ e.message << " (You may need to install the s3 gem)"
28
+ raise e
29
+ end
30
+
31
+ base.instance_eval do
32
+ @s3_credentials = parse_credentials(@options[:s3_credentials])
33
+ @bucket_name = @options[:bucket] || @s3_credentials[:bucket]
34
+ @bucket_name = @bucket_name.call(self) if @bucket_name.is_a?(Proc)
35
+ @s3_options = @options[:s3_options] || {}
36
+ @s3_permissions = @options[:s3_permissions] || :public_read
37
+ @s3_storage_class = @options[:s3_storage_class] || :standard
38
+ @s3_protocol = @options[:s3_protocol] || (@s3_permissions == :public_read ? "http" : "https")
39
+ @s3_headers = @options[:s3_headers] || {}
40
+ @s3_host_alias = @options[:s3_host_alias]
41
+ @url = ":s3_path_url" unless @url.to_s.match(/^:s3.*url$/)
42
+ @service = ::S3::Service.new(@s3_options.merge(
43
+ :access_key_id => @s3_credentials[:access_key_id],
44
+ :secret_access_key => @s3_credentials[:secret_access_key],
45
+ :use_ssl => @s3_protocol == "https"
46
+ ))
47
+ @bucket = @service.buckets.build(@bucket_name)
48
+ end
49
+ Paperclip.interpolates(:s3_alias_url) do |attachment, style|
50
+ "#{attachment.s3_protocol}://#{attachment.s3_host_alias}/#{attachment.path(style).gsub(%r{^/}, "")}"
51
+ end
52
+ Paperclip.interpolates(:s3_path_url) do |attachment, style|
53
+ "#{attachment.s3_protocol}://s3.amazonaws.com/#{attachment.bucket_name}/#{attachment.path(style).gsub(%r{^/}, "")}"
54
+ end
55
+ Paperclip.interpolates(:s3_domain_url) do |attachment, style|
56
+ "#{attachment.s3_protocol}://#{attachment.bucket_name}.s3.amazonaws.com/#{attachment.path(style).gsub(%r{^/}, "")}"
57
+ end
58
+ end
59
+
60
+ def expiring_url(style_name = default_style, time = 3600)
61
+ bucket.objects.build(path(style_name)).temporary_url(Time.now + time)
62
+ end
63
+
64
+ def bucket_name
65
+ @bucket_name
66
+ end
67
+
68
+ def bucket
69
+ @bucket
70
+ end
71
+
72
+ def s3_host_alias
73
+ @s3_host_alias
74
+ end
75
+
76
+ def parse_credentials creds
77
+ creds = find_credentials(creds).stringify_keys
78
+ (creds[RAILS_ENV] || creds).symbolize_keys
79
+ end
80
+
81
+ def exists?(style = default_style)
82
+ if original_filename
83
+ bucket.objects.build(path(style)).exists?
84
+ else
85
+ false
86
+ end
87
+ end
88
+
89
+ def s3_protocol
90
+ @s3_protocol
91
+ end
92
+
93
+ # Returns representation of the data of the file assigned to the given
94
+ # style, in the format most representative of the current storage.
95
+ def to_file style = default_style
96
+ return @queued_for_write[style] if @queued_for_write[style]
97
+ begin
98
+ file = Tempfile.new(path(style))
99
+ file.binmode if file.respond_to?(:binmode)
100
+ file.write(bucket.objects.find(path(style)).content)
101
+ file.rewind
102
+ rescue ::S3::Error::NoSuchKey
103
+ file.close if file.respond_to?(:close)
104
+ file = nil
105
+ end
106
+ file
107
+ end
108
+
109
+ def flush_writes #:nodoc:
110
+ @queued_for_write.each do |style, file|
111
+ begin
112
+ log("saving #{path(style)}")
113
+ object = bucket.objects.build(path(style))
114
+ object.content = file.read
115
+ object.acl = @s3_permissions
116
+ object.storage_class = @s3_storage_class
117
+ object.content_type = instance_read(:content_type)
118
+ object.content_disposition = @s3_headers[:content_disposition]
119
+ object.content_encoding = @s3_headers[:content_encoding]
120
+ object.save
121
+ rescue ::S3::Error::ResponseError => e
122
+ raise
123
+ end
124
+ end
125
+ @queued_for_write = {}
126
+ end
127
+
128
+ def flush_deletes #:nodoc:
129
+ @queued_for_delete.each do |path|
130
+ begin
131
+ log("deleting #{path}")
132
+ bucket.objects.find(path).destroy
133
+ rescue ::S3::Error::ResponseError
134
+ # Ignore this.
135
+ end
136
+ end
137
+ @queued_for_delete = []
138
+ end
139
+
140
+ def find_credentials creds
141
+ case creds
142
+ when File
143
+ YAML::load(ERB.new(File.read(creds.path)).result)
144
+ when String
145
+ YAML::load(ERB.new(File.read(creds)).result)
146
+ when Hash
147
+ creds
148
+ else
149
+ raise ArgumentError, "Credentials are not a path, file, or hash."
150
+ end
151
+ end
152
+ private :find_credentials
153
+
154
+ end
155
+ end
156
+ end