futuresinc-attachment_fu 1.0.4 → 1.0.5

Sign up to get free protection for your applications and to get access to all the features.
Files changed (28) hide show
  1. data/VERSION.yml +1 -1
  2. data/attachment_fu.gemspec +11 -11
  3. data/init.rb +1 -1
  4. data/lib/attachment_fu/backends/cloud_file_backend.rb +209 -0
  5. data/lib/attachment_fu/backends/db_file_backend.rb +37 -0
  6. data/lib/attachment_fu/backends/file_system_backend.rb +124 -0
  7. data/lib/attachment_fu/backends/s3_backend.rb +392 -0
  8. data/lib/attachment_fu/processors/core_image_processor.rb +55 -0
  9. data/lib/attachment_fu/processors/gd2_processor.rb +53 -0
  10. data/lib/attachment_fu/processors/image_science_processor.rb +60 -0
  11. data/lib/attachment_fu/processors/mini_magick_processor.rb +131 -0
  12. data/lib/attachment_fu/processors/rmagick_processor.rb +56 -0
  13. data/lib/attachment_fu.rb +543 -0
  14. data/test/backends/remote/cloudfiles_test.rb +3 -3
  15. data/test/backends/remote/s3_test.rb +3 -3
  16. data/test/basic_test.rb +2 -2
  17. data/test/extra_attachment_test.rb +2 -2
  18. metadata +11 -11
  19. data/lib/technoweenie/attachment_fu/backends/cloud_file_backend.rb +0 -211
  20. data/lib/technoweenie/attachment_fu/backends/db_file_backend.rb +0 -39
  21. data/lib/technoweenie/attachment_fu/backends/file_system_backend.rb +0 -126
  22. data/lib/technoweenie/attachment_fu/backends/s3_backend.rb +0 -394
  23. data/lib/technoweenie/attachment_fu/processors/core_image_processor.rb +0 -59
  24. data/lib/technoweenie/attachment_fu/processors/gd2_processor.rb +0 -54
  25. data/lib/technoweenie/attachment_fu/processors/image_science_processor.rb +0 -61
  26. data/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb +0 -132
  27. data/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb +0 -57
  28. data/lib/technoweenie/attachment_fu.rb +0 -545
data/VERSION.yml CHANGED
@@ -1,4 +1,4 @@
1
1
  ---
2
- :patch: 4
2
+ :patch: 5
3
3
  :major: 1
4
4
  :minor: 0
@@ -2,7 +2,7 @@
2
2
 
3
3
  Gem::Specification.new do |s|
4
4
  s.name = %q{attachment_fu}
5
- s.version = "1.0.4"
5
+ s.version = "1.0.5"
6
6
 
7
7
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
8
8
  s.authors = ["Technoweenie"]
@@ -24,17 +24,17 @@ Gem::Specification.new do |s|
24
24
  "attachment_fu.gemspec",
25
25
  "init.rb",
26
26
  "install.rb",
27
+ "lib/attachment_fu.rb",
28
+ "lib/attachment_fu/backends/cloud_file_backend.rb",
29
+ "lib/attachment_fu/backends/db_file_backend.rb",
30
+ "lib/attachment_fu/backends/file_system_backend.rb",
31
+ "lib/attachment_fu/backends/s3_backend.rb",
32
+ "lib/attachment_fu/processors/core_image_processor.rb",
33
+ "lib/attachment_fu/processors/gd2_processor.rb",
34
+ "lib/attachment_fu/processors/image_science_processor.rb",
35
+ "lib/attachment_fu/processors/mini_magick_processor.rb",
36
+ "lib/attachment_fu/processors/rmagick_processor.rb",
27
37
  "lib/geometry.rb",
28
- "lib/technoweenie/attachment_fu.rb",
29
- "lib/technoweenie/attachment_fu/backends/cloud_file_backend.rb",
30
- "lib/technoweenie/attachment_fu/backends/db_file_backend.rb",
31
- "lib/technoweenie/attachment_fu/backends/file_system_backend.rb",
32
- "lib/technoweenie/attachment_fu/backends/s3_backend.rb",
33
- "lib/technoweenie/attachment_fu/processors/core_image_processor.rb",
34
- "lib/technoweenie/attachment_fu/processors/gd2_processor.rb",
35
- "lib/technoweenie/attachment_fu/processors/image_science_processor.rb",
36
- "lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb",
37
- "lib/technoweenie/attachment_fu/processors/rmagick_processor.rb",
38
38
  "rackspace_cloudfiles.yml.tpl",
39
39
  "test/backends/db_file_test.rb",
40
40
  "test/backends/file_system_test.rb",
data/init.rb CHANGED
@@ -1 +1 @@
1
- require 'technoweenie/attachment_fu'
1
+ require File.dirname(__FILE__) + '/lib/attachment_fu'
@@ -0,0 +1,209 @@
1
+ module AttachmentFu # :nodoc:
2
+ module Backends
3
+ # = CloudFiles Storage Backend
4
+ #
5
+ # Enables use of {Rackspace Cloud Files}[http://www.mosso.com/cloudfiles.jsp] as a storage mechanism
6
+ #
7
+ # Based heavily on the Amazon S3 backend.
8
+ #
9
+ # == Requirements
10
+ #
11
+ # Requires the {Cloud Files Gem}[http://www.mosso.com/cloudfiles.jsp] by Rackspace
12
+ #
13
+ # == Configuration
14
+ #
15
+ # Configuration is done via <tt>RAILS_ROOT/config/rackspace_cloudfiles.yml</tt> and is loaded according to the <tt>RAILS_ENV</tt>.
16
+ # The minimum connection options that you must specify are a container name, your Mosso login name and your Mosso API key.
17
+ # You can sign up for Cloud Files and get access keys by visiting https://www.mosso.com/buy.htm
18
+ #
19
+ # Example configuration (RAILS_ROOT/config/rackspace_cloudfiles.yml)
20
+ #
21
+ # development:
22
+ # container_name: appname_development
23
+ # username: <your key>
24
+ # api_key: <your key>
25
+ #
26
+ # test:
27
+ # container_name: appname_test
28
+ # username: <your key>
29
+ # api_key: <your key>
30
+ #
31
+ # production:
32
+ # container_name: appname
33
+ # username: <your key>
34
+ # apik_key: <your key>
35
+ #
36
+ # You can change the location of the config path by passing a full path to the :cloudfiles_config_path option.
37
+ #
38
+ # has_attachment :storage => :cloud_files, :cloudfiles_config_path => (RAILS_ROOT + '/config/mosso.yml')
39
+ #
40
+ # === Required configuration parameters
41
+ #
42
+ # * <tt>:username</tt> - The username for your Rackspace Cloud (Mosso) account. Provided by Rackspace.
43
+ # * <tt>:secret_access_key</tt> - The api key for your Rackspace Cloud account. Provided by Rackspace.
44
+ # * <tt>:container_name</tt> - The name of a container in your Cloud Files account.
45
+ #
46
+ # If any of these required arguments is missing, a AuthenticationException will be raised from CloudFiles::Connection.
47
+ #
48
+ # == Usage
49
+ #
50
+ # To specify Cloud Files as the storage mechanism for a model, set the acts_as_attachment <tt>:storage</tt> option to <tt>:cloud_files/tt>.
51
+ #
52
+ # class Photo < ActiveRecord::Base
53
+ # has_attachment :storage => :cloud_files
54
+ # end
55
+ #
56
+ # === Customizing the path
57
+ #
58
+ # By default, files are prefixed using a pseudo hierarchy in the form of <tt>:table_name/:id</tt>, which results
59
+ # in Cloud Files object names (and urls) that look like: http://:server/:container_name/:table_name/:id/:filename with :table_name
60
+ # representing the customizable portion of the path. You can customize this prefix using the <tt>:path_prefix</tt>
61
+ # option:
62
+ #
63
+ # class Photo < ActiveRecord::Base
64
+ # has_attachment :storage => :cloud_files, :path_prefix => 'my/custom/path'
65
+ # end
66
+ #
67
+ # Which would result in public URLs like <tt>http(s)://:server/:container_name/my/custom/path/:id/:filename.</tt>
68
+ #
69
+ # === Permissions
70
+ #
71
+ # File permisisons are determined by the permissions of the container. At present, the options are public (and distributed
72
+ # by the Limelight CDN), and private (only available to your login)
73
+ #
74
+ # === Other options
75
+ #
76
+ # Of course, all the usual configuration options apply, such as content_type and thumbnails:
77
+ #
78
+ # class Photo < ActiveRecord::Base
79
+ # has_attachment :storage => :cloud_files, :content_type => ['application/pdf', :image], :resize_to => 'x50'
80
+ # has_attachment :storage => :cloud_files, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
81
+ # end
82
+ #
83
+ # === Accessing Cloud Files URLs
84
+ #
85
+ # You can get an object's public URL using the cloudfiles_url accessor. For example, assuming that for your postcard app
86
+ # you had a container name like 'postcard_world_development', and an attachment model called Photo:
87
+ #
88
+ # @postcard.cloudfiles_url # => http://cdn.cloudfiles.mosso.com/c45182/uploaded_files/20/london.jpg
89
+ #
90
+ # The resulting url is in the form: http://:server/:container_name/:table_name/:id/:file.
91
+ # The optional thumbnail argument will output the thumbnail's filename (if any).
92
+ #
93
+ # Additionally, you can get an object's base path relative to the container root using
94
+ # <tt>base_path</tt>:
95
+ #
96
+ # @photo.file_base_path # => uploaded_files/20
97
+ #
98
+ # And the full path (including the filename) using <tt>full_filename</tt>:
99
+ #
100
+ # @photo.full_filename # => uploaded_files/20/london.jpg
101
+ #
102
+ # Niether <tt>base_path</tt> or <tt>full_filename</tt> include the container name as part of the path.
103
+ # You can retrieve the container name using the <tt>container_name</tt> method.
104
+ module CloudFileBackend
105
+ class RequiredLibraryNotFoundError < StandardError; end
106
+ class ConfigFileNotFoundError < StandardError; end
107
+
108
+ def self.included(base) #:nodoc:
109
+ mattr_reader :container_name, :cloudfiles_config
110
+
111
+ begin
112
+ require 'cloudfiles'
113
+ rescue LoadError
114
+ raise RequiredLibraryNotFoundError.new('CloudFiles could not be loaded')
115
+ end
116
+
117
+ begin
118
+ @@cloudfiles_config_path = base.attachment_options[:cloudfiles_config_path] || (RAILS_ROOT + '/config/rackspace_cloudfiles.yml')
119
+ @@cloudfiles_config = @@cloudfiles_config = YAML.load(ERB.new(File.read(@@cloudfiles_config_path)).result)[RAILS_ENV].symbolize_keys
120
+ rescue
121
+ #raise ConfigFileNotFoundError.new('File %s not found' % @@cloudfiles_config_path)
122
+ end
123
+
124
+ @@container_name = @@cloudfiles_config[:container_name]
125
+ @@cf = CloudFiles::Connection.new(@@cloudfiles_config[:username], @@cloudfiles_config[:api_key])
126
+ @@container = @@cf.container(@@container_name)
127
+
128
+ base.before_update :rename_file
129
+ end
130
+
131
+ # Overwrites the base filename writer in order to store the old filename
132
+ def filename=(value)
133
+ @old_filename = filename unless filename.nil? || @old_filename
134
+ write_attribute :filename, sanitize_filename(value)
135
+ end
136
+
137
+ # The attachment ID used in the full path of a file
138
+ def attachment_path_id
139
+ ((respond_to?(:parent_id) && parent_id) || id).to_s
140
+ end
141
+
142
+ # The pseudo hierarchy containing the file relative to the container name
143
+ # Example: <tt>:table_name/:id</tt>
144
+ def base_path
145
+ File.join(attachment_options[:path_prefix], attachment_path_id)
146
+ end
147
+
148
+ # The full path to the file relative to the container name
149
+ # Example: <tt>:table_name/:id/:filename</tt>
150
+ def full_filename(thumbnail = nil)
151
+ File.join(base_path, thumbnail_name_for(thumbnail))
152
+ end
153
+
154
+ # All public objects are accessible via a GET request to the Cloud Files servers. You can generate a
155
+ # url for an object using the cloudfiles_url method.
156
+ #
157
+ # @photo.cloudfiles_url
158
+ #
159
+ # The resulting url is in the CDN URL for the object
160
+ #
161
+ # The optional thumbnail argument will output the thumbnail's filename (if any).
162
+ #
163
+ # If you are trying to get the URL for a nonpublic container, nil will be returned.
164
+ def cloudfiles_url(thumbnail = nil)
165
+ if @@container.public?
166
+ File.join(@@container.cdn_url, full_filename(thumbnail))
167
+ else
168
+ nil
169
+ end
170
+ end
171
+ alias :public_filename :cloudfiles_url
172
+
173
+ def create_temp_file
174
+ write_to_temp_file current_data
175
+ end
176
+
177
+ def current_data
178
+ @@container.get_object(full_filename).data
179
+ end
180
+
181
+ protected
182
+ # Called in the after_destroy callback
183
+ def destroy_file
184
+ @@container.delete_object(full_filename)
185
+ end
186
+
187
+ def rename_file
188
+ # Cloud Files doesn't rename right now, so we'll just nuke.
189
+ return unless @old_filename && @old_filename != filename
190
+
191
+ old_full_filename = File.join(base_path, @old_filename)
192
+ @@container.delete_object(old_full_filename)
193
+
194
+ @old_filename = nil
195
+ true
196
+ end
197
+
198
+ def save_to_storage
199
+ if save_attachment?
200
+ @object = @@container.create_object(full_filename)
201
+ @object.write((temp_path ? File.open(temp_path) : temp_data))
202
+ end
203
+
204
+ @old_filename = nil
205
+ true
206
+ end
207
+ end
208
+ end
209
+ end
@@ -0,0 +1,37 @@
1
+ module AttachmentFu # :nodoc:
2
+ module Backends
3
+ # Methods for DB backed attachments
4
+ module DbFileBackend
5
+ def self.included(base) #:nodoc:
6
+ Object.const_set(:DbFile, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:DbFile)
7
+ base.belongs_to :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
8
+ end
9
+
10
+ # Creates a temp file with the current db data.
11
+ def create_temp_file
12
+ write_to_temp_file current_data
13
+ end
14
+
15
+ # Gets the current data from the database
16
+ def current_data
17
+ db_file.data
18
+ end
19
+
20
+ protected
21
+ # Destroys the file. Called in the after_destroy callback
22
+ def destroy_file
23
+ db_file.destroy if db_file
24
+ end
25
+
26
+ # Saves the data to the DbFile model
27
+ def save_to_storage
28
+ if save_attachment?
29
+ (db_file || build_db_file).data = temp_data
30
+ db_file.save!
31
+ self.class.update_all ['db_file_id = ?', self.db_file_id = db_file.id], ['id = ?', id]
32
+ end
33
+ true
34
+ end
35
+ end
36
+ end
37
+ end
@@ -0,0 +1,124 @@
1
+ require 'fileutils'
2
+ require 'digest/sha2'
3
+
4
+ module AttachmentFu # :nodoc:
5
+ module Backends
6
+ # Methods for file system backed attachments
7
+ module FileSystemBackend
8
+ def self.included(base) #:nodoc:
9
+ base.before_update :rename_file
10
+ end
11
+
12
+ # Gets the full path to the filename in this format:
13
+ #
14
+ # # This assumes a model name like MyModel
15
+ # # public/#{table_name} is the default filesystem path
16
+ # RAILS_ROOT/public/my_models/5/blah.jpg
17
+ #
18
+ # Overwrite this method in your model to customize the filename.
19
+ # The optional thumbnail argument will output the thumbnail's filename.
20
+ def full_filename(thumbnail = nil)
21
+ file_system_path = (thumbnail ? thumbnail_class : self).attachment_options[:path_prefix].to_s
22
+ File.join(RAILS_ROOT, file_system_path, *partitioned_path(thumbnail_name_for(thumbnail)))
23
+ end
24
+
25
+ # Used as the base path that #public_filename strips off full_filename to create the public path
26
+ def base_path
27
+ @base_path ||= File.join(RAILS_ROOT, 'public')
28
+ end
29
+
30
+ # The attachment ID used in the full path of a file
31
+ def attachment_path_id
32
+ ((respond_to?(:parent_id) && parent_id) || id) || 0
33
+ end
34
+
35
+ # Partitions the given path into an array of path components.
36
+ #
37
+ # For example, given an <tt>*args</tt> of ["foo", "bar"], it will return
38
+ # <tt>["0000", "0001", "foo", "bar"]</tt> (assuming that that id returns 1).
39
+ #
40
+ # If the id is not an integer, then path partitioning will be performed by
41
+ # hashing the string value of the id with SHA-512, and splitting the result
42
+ # into 4 components. If the id a 128-bit UUID (as set by :uuid_primary_key => true)
43
+ # then it will be split into 2 components.
44
+ #
45
+ # To turn this off entirely, set :partition => false.
46
+ def partitioned_path(*args)
47
+ if respond_to?(:attachment_options) && attachment_options[:partition] == false
48
+ args
49
+ elsif attachment_options[:uuid_primary_key]
50
+ # Primary key is a 128-bit UUID in hex format. Split it into 2 components.
51
+ path_id = attachment_path_id.to_s
52
+ component1 = path_id[0..15] || "-"
53
+ component2 = path_id[16..-1] || "-"
54
+ [component1, component2] + args
55
+ else
56
+ path_id = attachment_path_id
57
+ if path_id.is_a?(Integer)
58
+ # Primary key is an integer. Split it after padding it with 0.
59
+ ("%08d" % path_id).scan(/..../) + args
60
+ else
61
+ # Primary key is a String. Hash it, then split it into 4 components.
62
+ hash = Digest::SHA512.hexdigest(path_id.to_s)
63
+ [hash[0..31], hash[32..63], hash[64..95], hash[96..127]] + args
64
+ end
65
+ end
66
+ end
67
+
68
+ # Gets the public path to the file
69
+ # The optional thumbnail argument will output the thumbnail's filename.
70
+ def public_filename(thumbnail = nil)
71
+ full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
72
+ end
73
+
74
+ def filename=(value)
75
+ @old_filename = full_filename unless filename.nil? || @old_filename
76
+ write_attribute :filename, sanitize_filename(value)
77
+ end
78
+
79
+ # Creates a temp file from the currently saved file.
80
+ def create_temp_file
81
+ copy_to_temp_file full_filename
82
+ end
83
+
84
+ protected
85
+ # Destroys the file. Called in the after_destroy callback
86
+ def destroy_file
87
+ FileUtils.rm full_filename
88
+ # remove directory also if it is now empty
89
+ Dir.rmdir(File.dirname(full_filename)) if (Dir.entries(File.dirname(full_filename))-['.','..']).empty?
90
+ rescue
91
+ logger.info "Exception destroying #{full_filename.inspect}: [#{$!.class.name}] #{$1.to_s}"
92
+ logger.warn $!.backtrace.collect { |b| " > #{b}" }.join("\n")
93
+ end
94
+
95
+ # Renames the given file before saving
96
+ def rename_file
97
+ return unless @old_filename && @old_filename != full_filename
98
+ if save_attachment? && File.exists?(@old_filename)
99
+ FileUtils.rm @old_filename
100
+ elsif File.exists?(@old_filename)
101
+ FileUtils.mv @old_filename, full_filename
102
+ end
103
+ @old_filename = nil
104
+ true
105
+ end
106
+
107
+ # Saves the file to the file system
108
+ def save_to_storage
109
+ if save_attachment?
110
+ # TODO: This overwrites the file if it exists, maybe have an allow_overwrite option?
111
+ FileUtils.mkdir_p(File.dirname(full_filename))
112
+ FileUtils.cp(temp_path, full_filename)
113
+ FileUtils.chmod(attachment_options[:chmod] || 0644, full_filename)
114
+ end
115
+ @old_filename = nil
116
+ true
117
+ end
118
+
119
+ def current_data
120
+ File.file?(full_filename) ? File.read(full_filename) : nil
121
+ end
122
+ end
123
+ end
124
+ end