steamcannon-s3 0.3.2

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/.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