futuresinc-attachment_fu 1.0.4 → 1.0.5

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