uploadcolumn 0.3.0

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.
@@ -0,0 +1,299 @@
1
+ module UploadColumn
2
+
3
+ class UploadError < StandardError #:nodoc:
4
+ end
5
+ class IntegrityError < UploadError #:nodoc:
6
+ end
7
+ class TemporaryPathMalformedError < UploadError #:nodoc:
8
+ end
9
+ class UploadNotMultipartError < UploadError #:nodoc:
10
+ end
11
+
12
+ TempValueRegexp = %r{^((?:\d+\.)+\d+)/([^/;]+)(?:;([^/;]+))?$}
13
+
14
+
15
+ # When you call an upload_column field, an instance of this class will be returned.
16
+ #
17
+ # Suppose a +User+ model has a +picture+ upload_column, like so:
18
+ # class User < ActiveRecord::Base
19
+ # upload_column :picture
20
+ # end
21
+ # Now in our controller we did:
22
+ # @user = User.find(params[:id])
23
+ # We could then access the file:
24
+ # @user.picture.url
25
+ # Which would output the url to the file (assuming it is stored in /public/)
26
+ # = Versions
27
+ # If we had instead added different versions in our model
28
+ # upload_column :picture, :versions => [:thumb, :large]
29
+ # Then we could access them like so:
30
+ # @user.picture.thumb.url
31
+ # See the +README+ for more detaills.
32
+ class UploadedFile < SanitizedFile
33
+
34
+ attr_reader :instance, :attribute, :options, :versions
35
+ attr_accessor :suffix
36
+
37
+ class << self
38
+
39
+ # upload a file. In most cases you want to pass the ActiveRecord instance and the attribute
40
+ # name as well as the file. For a more bare-bones approach, check out SanitizedFile.
41
+ def upload(file, instance = nil, attribute = nil, options = {}) #:nodoc:
42
+ uf = self.new(:upload, file, instance, attribute, options)
43
+ return uf.empty? ? nil : uf
44
+ end
45
+
46
+ # Retrieve a file from the filesystem, based on the calculated store_dir and the filename
47
+ # stored in the database.
48
+ def retrieve(filename, instance = nil, attribute = nil, options = {}) #:nodoc:
49
+ self.new(:retrieve, filename, instance, attribute, options)
50
+ end
51
+
52
+ # Retreieve a file that was stored as a temp file
53
+ def retrieve_temp(path, instance = nil, attribute = nil, options = {}) #:nodoc:
54
+ self.new(:retrieve_temp, path, instance, attribute, options)
55
+ end
56
+
57
+ end
58
+
59
+ def initialize(mode, file, instance, attribute, options={})
60
+ # TODO: the options are always reverse merged in here, in case UploadedFile has
61
+ # been initialized outside UploadColumn proper, this is not a very elegant solution, imho.
62
+ @options = options.reverse_merge(UploadColumn.configuration)
63
+ @instance = instance
64
+ @attribute = attribute
65
+ @suffix = options[:suffix]
66
+
67
+ load_manipulator
68
+
69
+ case mode
70
+ when :upload
71
+ if file and file.is_a?(String) and not file.empty?
72
+ raise UploadNotMultipartError.new("Do not know how to handle a string with value '#{file}' that was uploaded. Check if the form's encoding has been set to 'multipart/form-data'.")
73
+ end
74
+
75
+ super(file, @options)
76
+
77
+ unless empty?
78
+ if options[:validate_integrity]
79
+ raise UploadError.new("No list of valid extensions supplied.") unless options[:extensions]
80
+ raise IntegrityError.new("has an extension that is not allowed.") unless options[:extensions].include?(extension)
81
+ end
82
+
83
+ @temp_name = generate_tmpname
84
+ @new_file = true
85
+
86
+ move_to_directory(File.join(tmp_dir, @temp_name))
87
+
88
+ # The original is processed before versions are initialized.
89
+ self.process!(@options[:process]) if @options[:process] and self.respond_to?(:process!)
90
+
91
+ initialize_versions do |version|
92
+ copy_to_version(version)
93
+ end
94
+
95
+ apply_manipulations_to_versions
96
+
97
+ # trigger the _after_upload callback
98
+ self.instance.send("#{self.attribute}_after_upload", self) if self.instance.respond_to?("#{self.attribute}_after_upload")
99
+ end
100
+ when :retrieve
101
+ @path = File.join(store_dir, file)
102
+ @basename, @extension = split_extension(file)
103
+ initialize_versions
104
+ when :retrieve_temp
105
+ if file and not file.empty?
106
+ @temp_name, name, original_filename = file.scan( ::UploadColumn::TempValueRegexp ).first
107
+
108
+ if @temp_name and name
109
+ @path = File.join(tmp_dir, @temp_name, name)
110
+ @basename, @extension = split_extension(name)
111
+ @original_filename = original_filename
112
+ initialize_versions
113
+ else
114
+ raise TemporaryPathMalformedError.new("#{file} is not a valid temporary path!")
115
+ end
116
+ end
117
+ else
118
+ super(file, @options)
119
+ initialize_versions
120
+ end
121
+ end
122
+
123
+ # Returns the directory where tmp files are stored for this UploadedFile, relative to :root_dir
124
+ def relative_tmp_dir
125
+ parse_dir_options(:tmp_dir)
126
+ end
127
+
128
+ # Returns the directory where tmp files are stored for this UploadedFile
129
+ def tmp_dir
130
+ File.expand_path(self.relative_tmp_dir, @options[:root_dir])
131
+ end
132
+
133
+ # Returns the directory where files are stored for this UploadedFile, relative to :root_dir
134
+ def relative_store_dir
135
+ parse_dir_options(:store_dir)
136
+ end
137
+
138
+ # Returns the directory where files are stored for this UploadedFile
139
+ def store_dir
140
+ File.expand_path(self.relative_store_dir, @options[:root_dir])
141
+ end
142
+
143
+ # Returns the path of the file relative to :root_dir
144
+ def relative_path
145
+ self.path.sub(File.expand_path(options[:root_dir]) + '/', '')
146
+ end
147
+
148
+ # returns the full path of the file.
149
+ def path; super; end
150
+
151
+ # returns the directory where the file is currently stored.
152
+ def dir
153
+ File.dirname(self.path)
154
+ end
155
+
156
+ # return true if the file has just been uploaded.
157
+ def new_file?
158
+ @new_file
159
+ end
160
+
161
+ # returns the url of the file, by merging the relative path with the web_root option.
162
+ def public_path
163
+ # TODO: this might present an attack vector if the file is outside the web_root
164
+ options[:web_root].to_s + '/' + self.relative_path.gsub("\\", "/")
165
+ end
166
+
167
+ alias_method :to_s, :public_path
168
+ alias_method :url, :public_path
169
+
170
+ # this is the value returned when avatar_temp is called, where avatar is an upload_column
171
+ def temp_value #:nodoc:
172
+ if tempfile?
173
+ if original_filename
174
+ %(#{@temp_name}/#{filename};#{original_filename})
175
+ else
176
+ %(#{@temp_name}/#{filename})
177
+ end
178
+ end
179
+ end
180
+
181
+ def inspect #:nodoc:
182
+ "<UploadedFile: #{self.path}>"
183
+ end
184
+
185
+ def tempfile?
186
+ @temp_name
187
+ end
188
+
189
+ alias_method :actual_filename, :filename
190
+
191
+ def filename
192
+ unless bn = parse_dir_options(:filename)
193
+ bn = [self.basename, self.suffix].compact.join('-')
194
+ bn += ".#{self.extension}" unless self.extension.blank?
195
+ end
196
+ return bn
197
+ end
198
+
199
+ # TODO: this is a public method, should be specced
200
+ def move_to_directory(dir)
201
+ p = File.join(dir, self.filename)
202
+ if copy_file(p)
203
+ @path = p
204
+ end
205
+ end
206
+
207
+ private
208
+
209
+ def copy_to_version(version)
210
+ copy = self.clone
211
+ copy.suffix = version
212
+
213
+ if copy_file(File.join(self.dir, copy.filename))
214
+ return copy
215
+ end
216
+ end
217
+
218
+ def initialize_versions
219
+ if self.options[:versions]
220
+ @versions = {}
221
+
222
+ version_keys = options[:versions].is_a?(Hash) ? options[:versions].keys : options[:versions]
223
+
224
+ version_keys.each do |version|
225
+
226
+ version = version.to_sym
227
+
228
+ # Raise an error if the version name is a method on this class
229
+ raise ArgumentError.new("#{version} is an illegal name for an UploadColumn version.") if self.respond_to?(version)
230
+
231
+ if block_given?
232
+ @versions[version] = yield(version)
233
+ else
234
+ # Copy the file and store it in the versions array
235
+ # TODO: this might result in the manipulator not being loaded.
236
+ @versions[version] = self.clone #class.new(:open, File.join(self.dir, "#{self.basename}-#{version}.#{self.extension}"), instance, attribute, options.merge(:versions => nil, :suffix => version))
237
+ @versions[version].suffix = version
238
+ end
239
+
240
+ @versions[version].instance_eval { @path = File.join(self.dir, self.filename) } # ensure path is not cached
241
+
242
+ # Add the version methods to the instance
243
+ self.instance_eval <<-SRC
244
+ def #{version}
245
+ self.versions[:#{version}]
246
+ end
247
+ SRC
248
+ end
249
+ end
250
+ end
251
+
252
+ def load_manipulator
253
+ if options[:manipulator]
254
+ self.extend(options[:manipulator])
255
+ self.load_manipulator_dependencies if self.respond_to?(:load_manipulator_dependencies)
256
+ end
257
+ end
258
+
259
+ def apply_manipulations_to_versions
260
+ @versions.each do |k, v|
261
+ v.process! @options[:versions][k]
262
+ end if @options[:versions].is_a?(Hash)
263
+ end
264
+
265
+ def save
266
+ self.move_to_directory(self.store_dir)
267
+ self.versions.each { |version, file| file.move_to_directory(self.store_dir) } if self.versions
268
+ @new_file = false
269
+ @temp_name = nil
270
+ true
271
+ end
272
+
273
+ def parse_dir_options(option)
274
+ if self.instance.respond_to?("#{self.attribute}_#{option}")
275
+ self.instance.send("#{self.attribute}_#{option}", self)
276
+ else
277
+ option = @options[option]
278
+ if option.is_a?(Proc)
279
+ case option.arity
280
+ when 2
281
+ option.call(self.instance, self)
282
+ when 1
283
+ option.call(self.instance)
284
+ else
285
+ option.call
286
+ end
287
+ else
288
+ option
289
+ end
290
+ end
291
+ end
292
+
293
+ def generate_tmpname
294
+ now = Time.now
295
+ "#{now.to_i}.#{now.usec}.#{Process.pid}"
296
+ end
297
+
298
+ end
299
+ end
@@ -0,0 +1,12 @@
1
+ require File.join(File.dirname(__FILE__), 'upload_column', 'sanitized_file.rb')
2
+ require File.join(File.dirname(__FILE__), 'upload_column', 'uploaded_file.rb')
3
+ require File.join(File.dirname(__FILE__), 'upload_column', 'magic_columns.rb')
4
+ require File.join(File.dirname(__FILE__), 'upload_column', 'active_record_extension.rb')
5
+ require File.join(File.dirname(__FILE__), 'upload_column', 'manipulators', 'rmagick.rb')
6
+ require File.join(File.dirname(__FILE__), 'upload_column', 'manipulators', 'image_science.rb')
7
+ require File.join(File.dirname(__FILE__), 'upload_column', 'configuration.rb')
8
+
9
+ require File.join(File.expand_path(File.dirname(__FILE__)), "..", "init")
10
+
11
+ ActiveRecord::Base.send(:include, UploadColumn::ActiveRecordExtension)
12
+