attachment_zen 1.0.1
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.
- checksums.yaml +7 -0
- data/CHANGELOG +35 -0
- data/README.md +227 -0
- data/lib/attachment_fu.rb +8 -0
- data/lib/technoweenie/attachment_fu.rb +771 -0
- data/lib/technoweenie/attachment_fu/backends/backend_delegator.rb +19 -0
- data/lib/technoweenie/attachment_fu/backends/cloud_file_backend.rb +245 -0
- data/lib/technoweenie/attachment_fu/backends/db_file_backend.rb +36 -0
- data/lib/technoweenie/attachment_fu/backends/file_system_backend.rb +125 -0
- data/lib/technoweenie/attachment_fu/backends/mogile_fs_backend.rb +99 -0
- data/lib/technoweenie/attachment_fu/backends/s3_backend.rb +394 -0
- data/lib/technoweenie/attachment_fu/geometry.rb +93 -0
- data/lib/technoweenie/attachment_fu/processors/core_image_processor.rb +56 -0
- data/lib/technoweenie/attachment_fu/processors/gd2_processor.rb +52 -0
- data/lib/technoweenie/attachment_fu/processors/image_science_processor.rb +59 -0
- data/lib/technoweenie/attachment_fu/processors/mini_magick_processor.rb +126 -0
- data/lib/technoweenie/attachment_fu/processors/rmagick_processor.rb +54 -0
- data/vendor/red_artisan/core_image/filters/color.rb +27 -0
- data/vendor/red_artisan/core_image/filters/effects.rb +31 -0
- data/vendor/red_artisan/core_image/filters/perspective.rb +25 -0
- data/vendor/red_artisan/core_image/filters/quality.rb +25 -0
- data/vendor/red_artisan/core_image/filters/scale.rb +47 -0
- data/vendor/red_artisan/core_image/filters/watermark.rb +32 -0
- data/vendor/red_artisan/core_image/processor.rb +122 -0
- metadata +255 -0
@@ -0,0 +1,19 @@
|
|
1
|
+
module Technoweenie # :nodoc:
|
2
|
+
module AttachmentFu # :nodoc:
|
3
|
+
module Backends
|
4
|
+
class BackendDelegator < Delegator
|
5
|
+
attr_accessor :attachment_options
|
6
|
+
|
7
|
+
def initialize(obj, opts)
|
8
|
+
@obj = obj
|
9
|
+
@attachment_options = opts
|
10
|
+
end
|
11
|
+
|
12
|
+
def __getobj__
|
13
|
+
@obj
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
@@ -0,0 +1,245 @@
|
|
1
|
+
module Technoweenie # :nodoc:
|
2
|
+
module AttachmentFu # :nodoc:
|
3
|
+
module Backends
|
4
|
+
# = CloudFiles Storage Backend
|
5
|
+
#
|
6
|
+
# Enables use of {Rackspace Cloud Files}[http://www.mosso.com/cloudfiles.jsp] as a storage mechanism
|
7
|
+
#
|
8
|
+
# Based heavily on the Amazon S3 backend.
|
9
|
+
#
|
10
|
+
# == Requirements
|
11
|
+
#
|
12
|
+
# Requires the {Cloud Files Gem}[http://www.mosso.com/cloudfiles.jsp] by Rackspace
|
13
|
+
#
|
14
|
+
# == Configuration
|
15
|
+
#
|
16
|
+
# Configuration is done via <tt>RAILS_ROOT/config/rackspace_cloudfiles.yml</tt> and is loaded according to the <tt>RAILS_ENV</tt>.
|
17
|
+
# The minimum connection options that you must specify are a container name, your Mosso login name and your Mosso API key.
|
18
|
+
# You can sign up for Cloud Files and get access keys by visiting https://www.mosso.com/buy.htm
|
19
|
+
#
|
20
|
+
# Example configuration (RAILS_ROOT/config/rackspace_cloudfiles.yml)
|
21
|
+
#
|
22
|
+
# development:
|
23
|
+
# container_name: appname_development
|
24
|
+
# username: <your key>
|
25
|
+
# api_key: <your key>
|
26
|
+
#
|
27
|
+
# test:
|
28
|
+
# container_name: appname_test
|
29
|
+
# username: <your key>
|
30
|
+
# api_key: <your key>
|
31
|
+
#
|
32
|
+
# production:
|
33
|
+
# container_name: appname
|
34
|
+
# username: <your key>
|
35
|
+
# apik_key: <your key>
|
36
|
+
#
|
37
|
+
# You can change the location of the config path by passing a full path to the :cloudfiles_config_path option.
|
38
|
+
#
|
39
|
+
# has_attachment :storage => :cloud_files, :cloudfiles_config_path => (RAILS_ROOT + '/config/mosso.yml')
|
40
|
+
#
|
41
|
+
# === Required configuration parameters
|
42
|
+
#
|
43
|
+
# * <tt>:username</tt> - The username for your Rackspace Cloud (Mosso) account. Provided by Rackspace.
|
44
|
+
# * <tt>:secret_access_key</tt> - The api key for your Rackspace Cloud account. Provided by Rackspace.
|
45
|
+
# * <tt>:container_name</tt> - The name of a container in your Cloud Files account.
|
46
|
+
#
|
47
|
+
# If any of these required arguments is missing, a AuthenticationException will be raised from CloudFiles::Connection.
|
48
|
+
#
|
49
|
+
# == Usage
|
50
|
+
#
|
51
|
+
# 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>.
|
52
|
+
#
|
53
|
+
# class Photo < ActiveRecord::Base
|
54
|
+
# has_attachment :storage => :cloud_files
|
55
|
+
# end
|
56
|
+
#
|
57
|
+
# === Customizing the path
|
58
|
+
#
|
59
|
+
# By default, files are prefixed using a pseudo hierarchy in the form of <tt>:table_name/:id</tt>, which results
|
60
|
+
# in Cloud Files object names (and urls) that look like: http://:server/:container_name/:table_name/:id/:filename with :table_name
|
61
|
+
# representing the customizable portion of the path. You can customize this prefix using the <tt>:path_prefix</tt>
|
62
|
+
# option:
|
63
|
+
#
|
64
|
+
# class Photo < ActiveRecord::Base
|
65
|
+
# has_attachment :storage => :cloud_files, :path_prefix => 'my/custom/path'
|
66
|
+
# end
|
67
|
+
#
|
68
|
+
# Which would result in public URLs like <tt>http(s)://:server/:container_name/my/custom/path/:id/:filename.</tt>
|
69
|
+
#
|
70
|
+
# === Permissions
|
71
|
+
#
|
72
|
+
# File permisisons are determined by the permissions of the container. At present, the options are public (and distributed
|
73
|
+
# by the Limelight CDN), and private (only available to your login)
|
74
|
+
#
|
75
|
+
# === Other options
|
76
|
+
#
|
77
|
+
# Of course, all the usual configuration options apply, such as content_type and thumbnails:
|
78
|
+
#
|
79
|
+
# class Photo < ActiveRecord::Base
|
80
|
+
# has_attachment :storage => :cloud_files, :content_type => ['application/pdf', :image], :resize_to => 'x50'
|
81
|
+
# has_attachment :storage => :cloud_files, :thumbnails => { :thumb => [50, 50], :geometry => 'x50' }
|
82
|
+
# end
|
83
|
+
#
|
84
|
+
# === Accessing Cloud Files URLs
|
85
|
+
#
|
86
|
+
# You can get an object's public URL using the cloudfiles_url accessor. For example, assuming that for your postcard app
|
87
|
+
# you had a container name like 'postcard_world_development', and an attachment model called Photo:
|
88
|
+
#
|
89
|
+
# @postcard.cloudfiles_url # => http://cdn.cloudfiles.mosso.com/c45182/uploaded_files/20/london.jpg
|
90
|
+
#
|
91
|
+
# The resulting url is in the form: http://:server/:container_name/:table_name/:id/:file.
|
92
|
+
# The optional thumbnail argument will output the thumbnail's filename (if any).
|
93
|
+
#
|
94
|
+
# Additionally, you can get an object's base path relative to the container root using
|
95
|
+
# <tt>base_path</tt>:
|
96
|
+
#
|
97
|
+
# @photo.file_base_path # => uploaded_files/20
|
98
|
+
#
|
99
|
+
# And the full path (including the filename) using <tt>full_filename</tt>:
|
100
|
+
#
|
101
|
+
# @photo.full_filename # => uploaded_files/20/london.jpg
|
102
|
+
#
|
103
|
+
# Niether <tt>base_path</tt> or <tt>full_filename</tt> include the container name as part of the path.
|
104
|
+
# You can retrieve the container name using the <tt>container_name</tt> method.
|
105
|
+
class CloudFileBackend < BackendDelegator
|
106
|
+
class RequiredLibraryNotFoundError < StandardError; end
|
107
|
+
class ConfigFileNotFoundError < StandardError; end
|
108
|
+
|
109
|
+
cattr_reader :cloudfiles_config, :container_name
|
110
|
+
def self.included_in_base(base) #:nodoc:
|
111
|
+
|
112
|
+
begin
|
113
|
+
require 'cloudfiles'
|
114
|
+
rescue LoadError
|
115
|
+
raise RequiredLibraryNotFoundError.new('CloudFiles could not be loaded')
|
116
|
+
end
|
117
|
+
|
118
|
+
opts = base.attachment_options
|
119
|
+
if opts[:cloudfiles_options]
|
120
|
+
@@cloudfiles_config = opts[:cloudfiles_options]
|
121
|
+
elsif opts[:cloudfiles_username] && opts[:cloudfiles_api_key] && opts[:cloudfiles_container_name]
|
122
|
+
@@cloudfiles_config = {:container_name => opts[:cloudfiles_container_name],
|
123
|
+
:username => opts[:cloudfiles_username],
|
124
|
+
:api_key => opts[:cloudfiles_api_key]}
|
125
|
+
else
|
126
|
+
@@cloudfiles_config_path = base.attachment_options[:cloudfiles_config_path] || Rails.root.join('config/rackspace_cloudfiles.yml').to_s
|
127
|
+
@@cloudfiles_config = @@cloudfiles_config = YAML.load(ERB.new(File.read(@@cloudfiles_config_path)).result)[ENV['RAILS_ENV']].symbolize_keys
|
128
|
+
base.attachment_options[:cloudfiles_container_name] = @@cloudfiles_config[:container_name]
|
129
|
+
end
|
130
|
+
end
|
131
|
+
|
132
|
+
def container_name
|
133
|
+
return @obj.database_container_name if @obj.respond_to?(:database_container_name) && @obj.database_container_name
|
134
|
+
@@cloudfiles_config[:container_name]
|
135
|
+
end
|
136
|
+
|
137
|
+
def self.connection
|
138
|
+
@@cf ||= CloudFiles::Connection.new(@@cloudfiles_config)
|
139
|
+
end
|
140
|
+
|
141
|
+
def container
|
142
|
+
self.class.connection.container(container_name)
|
143
|
+
end
|
144
|
+
|
145
|
+
def cloudfiles_authtoken
|
146
|
+
self.class.connection.authtoken
|
147
|
+
end
|
148
|
+
|
149
|
+
def cloudfiles_storage_url
|
150
|
+
cx = self.class.connection
|
151
|
+
cx.storagescheme + "://" + (cx.storageport ? "" : ":#{cx.storageport}") + cx.storagehost + cx.storagepath
|
152
|
+
end
|
153
|
+
|
154
|
+
# Overwrites the base filename writer in order to store the old filename
|
155
|
+
def notify_rename
|
156
|
+
@old_filename = filename unless filename.nil? || @old_filename
|
157
|
+
end
|
158
|
+
|
159
|
+
# The attachment ID used in the full path of a file
|
160
|
+
def attachment_path_id
|
161
|
+
((respond_to?(:parent_id) && parent_id) || @obj.id).to_s
|
162
|
+
end
|
163
|
+
|
164
|
+
# The pseudo hierarchy containing the file relative to the container name
|
165
|
+
# Example: <tt>:table_name/:id</tt>
|
166
|
+
def base_path
|
167
|
+
File.join(attachment_options[:path_prefix], attachment_path_id)
|
168
|
+
end
|
169
|
+
|
170
|
+
# The full path to the file relative to the container name
|
171
|
+
# Example: <tt>:table_name/:id/:filename</tt>
|
172
|
+
def full_filename(thumbnail = nil)
|
173
|
+
File.join(base_path, thumbnail_name_for(thumbnail))
|
174
|
+
end
|
175
|
+
|
176
|
+
# All public objects are accessible via a GET request to the Cloud Files servers. You can generate a
|
177
|
+
# url for an object using the cloudfiles_url method.
|
178
|
+
#
|
179
|
+
# @photo.cloudfiles_url
|
180
|
+
#
|
181
|
+
# The resulting url is in the CDN URL for the object
|
182
|
+
#
|
183
|
+
# The optional thumbnail argument will output the thumbnail's filename (if any).
|
184
|
+
#
|
185
|
+
# If you are trying to get the URL for a nonpublic container, nil will be returned.
|
186
|
+
def cloudfiles_url(thumbnail = nil)
|
187
|
+
if container.public?
|
188
|
+
File.join(container.cdn_url, full_filename(thumbnail))
|
189
|
+
else
|
190
|
+
nil
|
191
|
+
end
|
192
|
+
end
|
193
|
+
alias :public_url :cloudfiles_url
|
194
|
+
|
195
|
+
def create_temp_file
|
196
|
+
write_to_temp_file current_data
|
197
|
+
end
|
198
|
+
|
199
|
+
def current_data
|
200
|
+
container.get_object(full_filename).data
|
201
|
+
end
|
202
|
+
|
203
|
+
# Called in the after_destroy callback
|
204
|
+
def destroy_file
|
205
|
+
retried = false
|
206
|
+
begin
|
207
|
+
container.delete_object(full_filename)
|
208
|
+
rescue CloudFiles::Exception::NoSuchObject => e
|
209
|
+
rescue CloudFiles::Exception::InvalidResponse => e
|
210
|
+
if retried
|
211
|
+
raise e
|
212
|
+
else
|
213
|
+
retried = true
|
214
|
+
retry
|
215
|
+
end
|
216
|
+
end
|
217
|
+
end
|
218
|
+
|
219
|
+
def rename_file
|
220
|
+
# Cloud Files doesn't rename right now, so we'll just nuke.
|
221
|
+
return unless @old_filename && @old_filename != filename
|
222
|
+
|
223
|
+
old_full_filename = File.join(base_path, @old_filename)
|
224
|
+
begin
|
225
|
+
container.delete_object(old_full_filename)
|
226
|
+
rescue CloudFiles::Exception::NoSuchObject => e
|
227
|
+
end
|
228
|
+
|
229
|
+
@old_filename = nil
|
230
|
+
true
|
231
|
+
end
|
232
|
+
|
233
|
+
def save_to_storage
|
234
|
+
if save_attachment?
|
235
|
+
@object = container.create_object(full_filename)
|
236
|
+
@object.write((temp_path ? File.open(temp_path) : temp_data))
|
237
|
+
end
|
238
|
+
|
239
|
+
@old_filename = nil
|
240
|
+
true
|
241
|
+
end
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
module Technoweenie # :nodoc:
|
2
|
+
module AttachmentFu # :nodoc:
|
3
|
+
module Backends
|
4
|
+
# Methods for DB backed attachments
|
5
|
+
class DbFileBackend < BackendDelegator
|
6
|
+
def self.included_in_base(base)
|
7
|
+
Object.const_set(:DbFile, Class.new(ActiveRecord::Base)) unless Object.const_defined?(:DbFile)
|
8
|
+
base.belongs_to :db_file, :class_name => '::DbFile', :foreign_key => 'db_file_id'
|
9
|
+
end
|
10
|
+
|
11
|
+
def rename_file ; end
|
12
|
+
|
13
|
+
# Gets the current data from the database
|
14
|
+
def current_data
|
15
|
+
db_file && db_file.data
|
16
|
+
end
|
17
|
+
|
18
|
+
# Destroys the file. Called in the after_destroy callback
|
19
|
+
def destroy_file
|
20
|
+
db_file.destroy if db_file
|
21
|
+
end
|
22
|
+
|
23
|
+
# Saves the data to the DbFile model
|
24
|
+
def save_to_storage
|
25
|
+
if save_attachment?
|
26
|
+
(db_file || build_db_file).data = temp_data
|
27
|
+
db_file.save!
|
28
|
+
@obj.db_file_id = db_file.id
|
29
|
+
@obj.class.where(id: @obj.id).update_all db_file_id: db_file.id
|
30
|
+
end
|
31
|
+
true
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
require 'fileutils'
|
2
|
+
require 'digest/sha2'
|
3
|
+
|
4
|
+
module Technoweenie # :nodoc:
|
5
|
+
module AttachmentFu # :nodoc:
|
6
|
+
module Backends
|
7
|
+
# Methods for file system backed attachments
|
8
|
+
class FileSystemBackend < BackendDelegator
|
9
|
+
def self.included_in_base(base)
|
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
|
+
Rails.root.join(file_system_path, *partitioned_path(thumbnail_name_for(thumbnail))).to_s
|
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 ||= Rails.root.join('public').to_s
|
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) || read_attribute(: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
|
+
partitioned_path_for_fixnum(path_id, args)
|
59
|
+
else
|
60
|
+
# Primary key is a String. Hash it, then split it into 4 components.
|
61
|
+
hash = Digest::SHA512.hexdigest(path_id.to_s)
|
62
|
+
[hash[0..31], hash[32..63], hash[64..95], hash[96..127]] + args
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def partitioned_path_for_fixnum(path_id, args)
|
68
|
+
if path_id <= 9999_9999
|
69
|
+
("%08d" % path_id).scan(/..../) + args
|
70
|
+
else
|
71
|
+
("%012d" % path_id).scan(/..../) + args
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
# Gets the public path to the file
|
76
|
+
# The optional thumbnail argument will output the thumbnail's filename.
|
77
|
+
def public_filename(thumbnail = nil)
|
78
|
+
full_filename(thumbnail).gsub %r(^#{Regexp.escape(base_path)}), ''
|
79
|
+
end
|
80
|
+
|
81
|
+
def notify_rename
|
82
|
+
@old_filename = full_filename unless filename.nil? || @old_filename
|
83
|
+
end
|
84
|
+
|
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
|
125
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
module Technoweenie # :nodoc:
|
2
|
+
module AttachmentFu # :nodoc:
|
3
|
+
module Backends
|
4
|
+
class MogileFSBackend < BackendDelegator
|
5
|
+
class ConfigFileNotFoundError < StandardError; end
|
6
|
+
|
7
|
+
class RequiredLibraryNotFoundError < StandardError; end
|
8
|
+
attr_reader :mogile_domain_name
|
9
|
+
def initialize(obj, opts)
|
10
|
+
@domain_name = opts[:mogile_domain_name] || @@mogile_config[:domain_name]
|
11
|
+
super(obj, opts)
|
12
|
+
end
|
13
|
+
|
14
|
+
def self.included_in_base(base) #:nodoc:
|
15
|
+
begin
|
16
|
+
require 'mogilefs'
|
17
|
+
rescue LoadError
|
18
|
+
raise RequiredLibraryNotFoundError.new('mogilefs could not be loaded')
|
19
|
+
end
|
20
|
+
|
21
|
+
mogile_config = nil
|
22
|
+
if base.attachment_options[:mogile_hosts] && base.attachment_options[:mogile_domain_name]
|
23
|
+
mogile_config = base.attachment_options.inject({}) do |memo, arr|
|
24
|
+
k, v = arr
|
25
|
+
memo[k.to_s.gsub(/^mogile_/, '').to_sym] = v if k.to_s =~ /^mogile_/
|
26
|
+
memo
|
27
|
+
end
|
28
|
+
else
|
29
|
+
mogile_config_path = base.attachment_options[:mogile_config_path] || Rails.root.join('config/mogilefs.yml').to_s
|
30
|
+
mogile_config = YAML.load(ERB.new(File.read(mogile_config_path)).result)[RAILS_ENV].symbolize_keys
|
31
|
+
end
|
32
|
+
|
33
|
+
@@mogile_config = mogile_config
|
34
|
+
@@mogile = MogileFS::MogileFS.new(:domain => @@mogile_config[:domain_name], :hosts => @@mogile_config[:hosts])
|
35
|
+
end
|
36
|
+
|
37
|
+
# called by the ActiveRecord class from filename=
|
38
|
+
def notify_rename
|
39
|
+
@old_filename = filename unless filename.nil? || @old_filename
|
40
|
+
end
|
41
|
+
|
42
|
+
# The attachment ID used in the full path of a file
|
43
|
+
def attachment_path_id
|
44
|
+
((respond_to?(:parent_id) && parent_id) || @obj.id).to_s
|
45
|
+
end
|
46
|
+
|
47
|
+
# The pseudo hierarchy containing the file relative to the bucket name
|
48
|
+
# Example: <tt>:table_name/:id</tt>
|
49
|
+
def base_path
|
50
|
+
File.join(attachment_options[:path_prefix], attachment_path_id)
|
51
|
+
end
|
52
|
+
|
53
|
+
def full_filename(thumbnail = nil)
|
54
|
+
File.join(base_path, thumbnail_name_for(thumbnail))
|
55
|
+
end
|
56
|
+
|
57
|
+
def current_data
|
58
|
+
@@mogile.get_file_data(full_filename)
|
59
|
+
end
|
60
|
+
|
61
|
+
# Called in the after_destroy callback
|
62
|
+
def destroy_file
|
63
|
+
@@mogile.delete(full_filename)
|
64
|
+
end
|
65
|
+
|
66
|
+
def rename_file
|
67
|
+
return unless @old_filename && @old_filename != filename
|
68
|
+
|
69
|
+
old_full_filename = File.join(base_path, @old_filename)
|
70
|
+
|
71
|
+
begin
|
72
|
+
@@mogile.rename(old_full_filename, full_filename)
|
73
|
+
rescue MogileFS::Backend::KeyExistsError
|
74
|
+
# this is hacky. It's at first blush actually a limitation of the mogilefs-client gem,
|
75
|
+
# which always tries to create a new key instead of exposing "set" semantics.
|
76
|
+
@@mogile.delete(full_filename)
|
77
|
+
retry
|
78
|
+
end
|
79
|
+
|
80
|
+
@old_filename = nil
|
81
|
+
true
|
82
|
+
end
|
83
|
+
|
84
|
+
def save_to_storage
|
85
|
+
if save_attachment?
|
86
|
+
if temp_path
|
87
|
+
@@mogile.store_file(full_filename, @@mogile_config[:mogile_storage_class], temp_path)
|
88
|
+
else
|
89
|
+
@@mogile.store_content(full_filename, @@mogile_config[:mogile_storage_class], temp_data)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@old_filename = nil
|
94
|
+
true
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|