oahu-dragonfly 0.8.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.
Files changed (159) hide show
  1. data/.rspec +1 -0
  2. data/.yardopts +24 -0
  3. data/Gemfile +30 -0
  4. data/History.md +323 -0
  5. data/LICENSE +20 -0
  6. data/README.md +88 -0
  7. data/Rakefile +50 -0
  8. data/VERSION +1 -0
  9. data/config.ru +14 -0
  10. data/docs.watchr +1 -0
  11. data/dragonfly.gemspec +297 -0
  12. data/extra_docs/Analysers.md +66 -0
  13. data/extra_docs/Caching.md +23 -0
  14. data/extra_docs/Configuration.md +124 -0
  15. data/extra_docs/Couch.md +49 -0
  16. data/extra_docs/DataStorage.md +153 -0
  17. data/extra_docs/Encoding.md +67 -0
  18. data/extra_docs/GeneralUsage.md +121 -0
  19. data/extra_docs/Generators.md +60 -0
  20. data/extra_docs/Heroku.md +50 -0
  21. data/extra_docs/ImageMagick.md +125 -0
  22. data/extra_docs/Index.md +33 -0
  23. data/extra_docs/MimeTypes.md +40 -0
  24. data/extra_docs/Models.md +272 -0
  25. data/extra_docs/Mongo.md +45 -0
  26. data/extra_docs/Processing.md +77 -0
  27. data/extra_docs/Rack.md +52 -0
  28. data/extra_docs/Rails2.md +57 -0
  29. data/extra_docs/Rails3.md +62 -0
  30. data/extra_docs/Sinatra.md +25 -0
  31. data/extra_docs/URLs.md +169 -0
  32. data/features/images.feature +47 -0
  33. data/features/no_processing.feature +14 -0
  34. data/features/rails_3.0.5.feature +8 -0
  35. data/features/steps/common_steps.rb +8 -0
  36. data/features/steps/dragonfly_steps.rb +66 -0
  37. data/features/steps/rails_steps.rb +28 -0
  38. data/features/support/env.rb +13 -0
  39. data/features/support/setup.rb +32 -0
  40. data/fixtures/rails_3.0.5/files/app/models/album.rb +7 -0
  41. data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
  42. data/fixtures/rails_3.0.5/files/app/views/albums/show.html.erb +6 -0
  43. data/fixtures/rails_3.0.5/files/config/initializers/dragonfly.rb +4 -0
  44. data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
  45. data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
  46. data/fixtures/rails_3.0.5/files/features/step_definitions/image_steps.rb +25 -0
  47. data/fixtures/rails_3.0.5/files/features/support/paths.rb +17 -0
  48. data/fixtures/rails_3.0.5/files/features/text_images.feature +7 -0
  49. data/fixtures/rails_3.0.5/template.rb +20 -0
  50. data/irbrc.rb +18 -0
  51. data/lib/dragonfly.rb +55 -0
  52. data/lib/dragonfly/active_model_extensions.rb +13 -0
  53. data/lib/dragonfly/active_model_extensions/attachment.rb +250 -0
  54. data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +148 -0
  55. data/lib/dragonfly/active_model_extensions/class_methods.rb +95 -0
  56. data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
  57. data/lib/dragonfly/active_model_extensions/validations.rb +41 -0
  58. data/lib/dragonfly/analyser.rb +58 -0
  59. data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
  60. data/lib/dragonfly/analysis/image_magick_analyser.rb +6 -0
  61. data/lib/dragonfly/app.rb +172 -0
  62. data/lib/dragonfly/config/heroku.rb +19 -0
  63. data/lib/dragonfly/config/image_magick.rb +6 -0
  64. data/lib/dragonfly/config/rails.rb +20 -0
  65. data/lib/dragonfly/configurable.rb +207 -0
  66. data/lib/dragonfly/core_ext/array.rb +7 -0
  67. data/lib/dragonfly/core_ext/hash.rb +7 -0
  68. data/lib/dragonfly/core_ext/object.rb +12 -0
  69. data/lib/dragonfly/core_ext/string.rb +9 -0
  70. data/lib/dragonfly/core_ext/symbol.rb +9 -0
  71. data/lib/dragonfly/data_storage.rb +9 -0
  72. data/lib/dragonfly/data_storage/couch_data_store.rb +64 -0
  73. data/lib/dragonfly/data_storage/file_data_store.rb +141 -0
  74. data/lib/dragonfly/data_storage/mongo_data_store.rb +86 -0
  75. data/lib/dragonfly/data_storage/s3data_store.rb +145 -0
  76. data/lib/dragonfly/encoder.rb +13 -0
  77. data/lib/dragonfly/encoding/image_magick_encoder.rb +6 -0
  78. data/lib/dragonfly/function_manager.rb +71 -0
  79. data/lib/dragonfly/generation/image_magick_generator.rb +6 -0
  80. data/lib/dragonfly/generator.rb +9 -0
  81. data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
  82. data/lib/dragonfly/image_magick/analyser.rb +51 -0
  83. data/lib/dragonfly/image_magick/config.rb +41 -0
  84. data/lib/dragonfly/image_magick/encoder.rb +57 -0
  85. data/lib/dragonfly/image_magick/generator.rb +145 -0
  86. data/lib/dragonfly/image_magick/processor.rb +99 -0
  87. data/lib/dragonfly/image_magick/utils.rb +72 -0
  88. data/lib/dragonfly/image_magick_utils.rb +4 -0
  89. data/lib/dragonfly/job.rb +451 -0
  90. data/lib/dragonfly/job_builder.rb +39 -0
  91. data/lib/dragonfly/job_definitions.rb +26 -0
  92. data/lib/dragonfly/job_endpoint.rb +15 -0
  93. data/lib/dragonfly/loggable.rb +28 -0
  94. data/lib/dragonfly/middleware.rb +20 -0
  95. data/lib/dragonfly/processing/image_magick_processor.rb +6 -0
  96. data/lib/dragonfly/processor.rb +9 -0
  97. data/lib/dragonfly/rails/images.rb +27 -0
  98. data/lib/dragonfly/response.rb +97 -0
  99. data/lib/dragonfly/routed_endpoint.rb +40 -0
  100. data/lib/dragonfly/serializer.rb +32 -0
  101. data/lib/dragonfly/server.rb +113 -0
  102. data/lib/dragonfly/simple_cache.rb +23 -0
  103. data/lib/dragonfly/temp_object.rb +175 -0
  104. data/lib/dragonfly/url_mapper.rb +78 -0
  105. data/samples/beach.png +0 -0
  106. data/samples/egg.png +0 -0
  107. data/samples/round.gif +0 -0
  108. data/samples/sample.docx +0 -0
  109. data/samples/taj.jpg +0 -0
  110. data/spec/dragonfly/active_model_extensions/model_spec.rb +1426 -0
  111. data/spec/dragonfly/active_model_extensions/spec_helper.rb +91 -0
  112. data/spec/dragonfly/analyser_spec.rb +123 -0
  113. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +48 -0
  114. data/spec/dragonfly/app_spec.rb +135 -0
  115. data/spec/dragonfly/configurable_spec.rb +461 -0
  116. data/spec/dragonfly/core_ext/array_spec.rb +19 -0
  117. data/spec/dragonfly/core_ext/hash_spec.rb +19 -0
  118. data/spec/dragonfly/core_ext/string_spec.rb +17 -0
  119. data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
  120. data/spec/dragonfly/data_storage/couch_data_store_spec.rb +76 -0
  121. data/spec/dragonfly/data_storage/file_data_store_spec.rb +296 -0
  122. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +57 -0
  123. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +258 -0
  124. data/spec/dragonfly/data_storage/shared_data_store_examples.rb +77 -0
  125. data/spec/dragonfly/function_manager_spec.rb +154 -0
  126. data/spec/dragonfly/hash_with_css_style_keys_spec.rb +24 -0
  127. data/spec/dragonfly/image_magick/analyser_spec.rb +64 -0
  128. data/spec/dragonfly/image_magick/encoder_spec.rb +41 -0
  129. data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
  130. data/spec/dragonfly/image_magick/processor_spec.rb +233 -0
  131. data/spec/dragonfly/image_magick/utils_spec.rb +18 -0
  132. data/spec/dragonfly/job_builder_spec.rb +37 -0
  133. data/spec/dragonfly/job_definitions_spec.rb +35 -0
  134. data/spec/dragonfly/job_endpoint_spec.rb +173 -0
  135. data/spec/dragonfly/job_spec.rb +1046 -0
  136. data/spec/dragonfly/loggable_spec.rb +80 -0
  137. data/spec/dragonfly/middleware_spec.rb +47 -0
  138. data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
  139. data/spec/dragonfly/serializer_spec.rb +61 -0
  140. data/spec/dragonfly/server_spec.rb +278 -0
  141. data/spec/dragonfly/simple_cache_spec.rb +27 -0
  142. data/spec/dragonfly/temp_object_spec.rb +306 -0
  143. data/spec/dragonfly/url_mapper_spec.rb +126 -0
  144. data/spec/functional/deprecations_spec.rb +51 -0
  145. data/spec/functional/image_magick_app_spec.rb +27 -0
  146. data/spec/functional/model_urls_spec.rb +85 -0
  147. data/spec/functional/remote_on_the_fly_spec.rb +51 -0
  148. data/spec/functional/to_response_spec.rb +31 -0
  149. data/spec/spec_helper.rb +51 -0
  150. data/spec/support/argument_matchers.rb +19 -0
  151. data/spec/support/image_matchers.rb +47 -0
  152. data/spec/support/simple_matchers.rb +53 -0
  153. data/yard/handlers/configurable_attr_handler.rb +38 -0
  154. data/yard/setup.rb +15 -0
  155. data/yard/templates/default/fulldoc/html/css/common.css +107 -0
  156. data/yard/templates/default/layout/html/layout.erb +89 -0
  157. data/yard/templates/default/module/html/configuration_summary.erb +31 -0
  158. data/yard/templates/default/module/setup.rb +17 -0
  159. metadata +544 -0
@@ -0,0 +1,72 @@
1
+ require 'tempfile'
2
+
3
+ module Dragonfly
4
+ module ImageMagick
5
+ module Utils
6
+
7
+ # Exceptions
8
+ class ShellCommandFailed < RuntimeError; end
9
+
10
+ include Loggable
11
+ include Configurable
12
+ configurable_attr :convert_command, "convert"
13
+ configurable_attr :identify_command, "identify"
14
+ configurable_attr :log_commands, false
15
+
16
+ private
17
+
18
+ def convert(temp_object=nil, args='', format=nil)
19
+ tempfile = new_tempfile(format)
20
+ run "#{convert_command} #{args} #{temp_object.path if temp_object} #{tempfile.path}"
21
+ tempfile
22
+ end
23
+
24
+ def identify(temp_object)
25
+ # example of details string:
26
+ # myimage.png PNG 200x100 200x100+0+0 8-bit DirectClass 31.2kb
27
+ details = raw_identify(temp_object)
28
+ filename, format, geometry, geometry_2, depth, image_class, size = details.split(' ')
29
+ width, height = geometry.split('x')
30
+ {
31
+ :filename => filename,
32
+ :format => format.downcase.to_sym,
33
+ :width => width.to_i,
34
+ :height => height.to_i,
35
+ :depth => depth.to_i,
36
+ :image_class => image_class
37
+ }
38
+ end
39
+
40
+ def raw_identify(temp_object, args='')
41
+ run "#{identify_command} #{args} #{temp_object.path}"
42
+ end
43
+
44
+ def new_tempfile(ext=nil)
45
+ tempfile = ext ? Tempfile.new(['dragonfly', ".#{ext}"]) : Tempfile.new('dragonfly')
46
+ tempfile.binmode
47
+ tempfile.close
48
+ tempfile
49
+ end
50
+
51
+ def run(command)
52
+ log.debug("Running command: #{command}") if log_commands
53
+ begin
54
+ result = `#{command}`
55
+ rescue Errno::ENOENT
56
+ raise_shell_command_failed(command)
57
+ end
58
+ if $?.exitstatus == 1
59
+ throw :unable_to_handle
60
+ elsif !$?.success?
61
+ raise_shell_command_failed(command)
62
+ end
63
+ result
64
+ end
65
+
66
+ def raise_shell_command_failed(command)
67
+ raise ShellCommandFailed, "Command failed (#{command}) with exit status #{$?.exitstatus}"
68
+ end
69
+
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,4 @@
1
+ module Dragonfly
2
+ puts "WARNING: Dragonfly::ImageMagickUtils is DEPRECATED and will soon be removed. Please use Dragonfly::ImageMagick::Utils instead."
3
+ ImageMagickUtils = ImageMagick::Utils
4
+ end
@@ -0,0 +1,451 @@
1
+ require 'forwardable'
2
+ require 'digest/sha1'
3
+ require 'base64'
4
+ require 'open-uri'
5
+ require 'pathname'
6
+
7
+ module Dragonfly
8
+ class Job
9
+
10
+ # Exceptions
11
+ class AppDoesNotMatch < StandardError; end
12
+ class JobAlreadyApplied < StandardError; end
13
+ class NothingToProcess < StandardError; end
14
+ class NothingToEncode < StandardError; end
15
+ class NothingToAnalyse < StandardError; end
16
+ class InvalidArray < StandardError; end
17
+ class NoSHAGiven < StandardError; end
18
+ class IncorrectSHA < StandardError; end
19
+
20
+ extend Forwardable
21
+ def_delegators :result,
22
+ :data, :file, :tempfile, :path, :to_file, :size
23
+ def_delegator :app, :server
24
+
25
+ class Step
26
+
27
+ class << self
28
+ # Dragonfly::Job::Fetch -> 'Fetch'
29
+ def basename
30
+ @basename ||= name.split('::').last
31
+ end
32
+ # Dragonfly::Job::Fetch -> :fetch
33
+ def step_name
34
+ @step_name ||= basename.gsub(/[A-Z]/){ "_#{$&.downcase}" }.sub('_','').to_sym
35
+ end
36
+ # Dragonfly::Job::Fetch -> :f
37
+ def abbreviation
38
+ @abbreviation ||= basename.scan(/[A-Z]/).join.downcase.to_sym
39
+ end
40
+ end
41
+
42
+ def initialize(job, *args)
43
+ @job, @args = job, args
44
+ init
45
+ end
46
+
47
+ def init # To be overridden
48
+ end
49
+
50
+ attr_reader :job, :args
51
+
52
+ def inspect
53
+ "#{self.class.step_name}(#{args.map{|a| a.inspect }.join(', ')})"
54
+ end
55
+
56
+ end
57
+
58
+ class Fetch < Step
59
+ def uid
60
+ args.first
61
+ end
62
+ def apply
63
+ content, meta = job.app.datastore.retrieve(uid)
64
+ job.update(content, meta)
65
+ end
66
+ end
67
+
68
+ class Process < Step
69
+ def name
70
+ args.first
71
+ end
72
+ def arguments
73
+ args[1..-1]
74
+ end
75
+ def apply
76
+ raise NothingToProcess, "Can't process because temp object has not been initialized. Need to fetch first?" unless job.temp_object
77
+ content, meta = job.app.processor.process(job.temp_object, name, *arguments)
78
+ job.update(content, meta)
79
+ end
80
+ end
81
+
82
+ class Encode < Step
83
+ def init
84
+ job.meta[:format] = format
85
+ end
86
+ def format
87
+ args.first
88
+ end
89
+ def arguments
90
+ args[1..-1]
91
+ end
92
+ def apply
93
+ raise NothingToEncode, "Can't encode because temp object has not been initialized. Need to fetch first?" unless job.temp_object
94
+ content, meta = job.app.encoder.encode(job.temp_object, format, *arguments)
95
+ job.update(content, meta)
96
+ job.meta[:format] = format
97
+ end
98
+ end
99
+
100
+ class Generate < Step
101
+ def apply
102
+ content, meta = job.app.generator.generate(*args)
103
+ job.update(content, meta)
104
+ end
105
+ end
106
+
107
+ class FetchFile < Step
108
+ def init
109
+ job.name = File.basename(path)
110
+ end
111
+ def path
112
+ File.expand_path(args.first)
113
+ end
114
+ def apply
115
+ job.temp_object = TempObject.new(Pathname.new(path))
116
+ end
117
+ end
118
+
119
+ class FetchUrl < Step
120
+ def init
121
+ job.name = File.basename(path) if path[/[^\/]$/]
122
+ end
123
+ def url
124
+ @url ||= (args.first[%r<^\w+://>] ? args.first : "http://#{args.first}")
125
+ end
126
+ def path
127
+ @path ||= URI.parse(url).path
128
+ end
129
+ def apply
130
+ open(url) do |f|
131
+ job.temp_object = TempObject.new(f.read)
132
+ end
133
+ end
134
+ end
135
+
136
+ STEPS = [
137
+ Fetch,
138
+ Process,
139
+ Encode,
140
+ Generate,
141
+ FetchFile,
142
+ FetchUrl
143
+ ]
144
+
145
+ # Class methods
146
+ class << self
147
+
148
+ def from_a(steps_array, app)
149
+ unless steps_array.is_a?(Array) &&
150
+ steps_array.all?{|s| s.is_a?(Array) && step_abbreviations[s.first] }
151
+ raise InvalidArray, "can't define a job from #{steps_array.inspect}"
152
+ end
153
+ job = app.new_job
154
+ steps_array.each do |step_array|
155
+ step_class = step_abbreviations[step_array.shift]
156
+ job.steps << step_class.new(job, *step_array)
157
+ end
158
+ job
159
+ end
160
+
161
+ def deserialize(string, app)
162
+ from_a(Serializer.marshal_decode(string), app)
163
+ end
164
+
165
+ def step_abbreviations
166
+ @step_abbreviations ||= STEPS.inject({}){|hash, step_class| hash[step_class.abbreviation] = step_class; hash }
167
+ end
168
+
169
+ def step_names
170
+ @step_names ||= STEPS.map{|step_class| step_class.step_name }
171
+ end
172
+
173
+ end
174
+
175
+ ####### Instance methods #######
176
+
177
+ # This is needed because we need a way of overriding
178
+ # the methods added to Job objects by the analyser and by
179
+ # the job shortcuts like 'thumb', etc.
180
+ # If we had traits/classboxes in ruby maybe this wouldn't be needed
181
+ # Think of it as like a normal instance method but with a css-like !important after it
182
+ module OverrideInstanceMethods
183
+
184
+ def format
185
+ apply
186
+ format_from_meta || analyse(:format)
187
+ end
188
+
189
+ def mime_type
190
+ app.mime_type_for(format) || analyse(:mime_type) || app.fallback_mime_type
191
+ end
192
+
193
+ def to_s
194
+ super.sub(/#<Class:\w+>/, 'Extended Dragonfly::Job')
195
+ end
196
+
197
+ end
198
+
199
+ def initialize(app, content=nil, meta={})
200
+ @app = app
201
+ @steps = []
202
+ @next_step_index = 0
203
+ @meta = {}
204
+ update(content, meta)
205
+ end
206
+
207
+ # Used by 'dup' and 'clone'
208
+ def initialize_copy(other)
209
+ self.steps = other.steps.map do |step|
210
+ step.class.new(self, *step.args)
211
+ end
212
+ end
213
+
214
+ attr_accessor :temp_object
215
+ attr_reader :app, :steps
216
+
217
+ # define fetch(), fetch!(), process(), etc.
218
+ STEPS.each do |step_class|
219
+ class_eval %(
220
+ def #{step_class.step_name}(*args)
221
+ new_job = self.dup
222
+ new_job.steps << #{step_class}.new(new_job, *args)
223
+ new_job
224
+ end
225
+
226
+ def #{step_class.step_name}!(*args)
227
+ steps << #{step_class}.new(self, *args)
228
+ self
229
+ end
230
+ )
231
+ end
232
+
233
+ def analyse(method, *args)
234
+ unless result
235
+ raise NothingToAnalyse, "Can't analyse because temp object has not been initialized. Need to fetch first?"
236
+ end
237
+ analyser.analyse(result, method, *args)
238
+ end
239
+
240
+ # Applying, etc.
241
+
242
+ def apply
243
+ pending_steps.each{|step| step.apply }
244
+ self.next_step_index = steps.length
245
+ self
246
+ end
247
+
248
+ def applied?
249
+ next_step_index == steps.length
250
+ end
251
+
252
+ def result
253
+ apply
254
+ temp_object
255
+ end
256
+
257
+ def applied_steps
258
+ steps[0...next_step_index]
259
+ end
260
+
261
+ def pending_steps
262
+ steps[next_step_index..-1]
263
+ end
264
+
265
+ def to_a
266
+ steps.map{|step|
267
+ [step.class.abbreviation, *step.args]
268
+ }
269
+ end
270
+
271
+ # Serializing, etc.
272
+
273
+ def to_unique_s
274
+ to_a.to_dragonfly_unique_s
275
+ end
276
+
277
+ def serialize
278
+ Serializer.marshal_encode(to_a)
279
+ end
280
+
281
+ def unique_signature
282
+ Digest::SHA1.hexdigest(to_unique_s)
283
+ end
284
+
285
+ def sha
286
+ Digest::SHA1.hexdigest("#{to_unique_s}#{app.secret}")[0...8]
287
+ end
288
+
289
+ def validate_sha!(sha)
290
+ case sha
291
+ when nil
292
+ raise NoSHAGiven
293
+ when self.sha
294
+ self
295
+ else
296
+ raise IncorrectSHA, sha
297
+ end
298
+ end
299
+
300
+ # URLs, etc.
301
+
302
+ def url(opts={})
303
+ app.url_for(self, attributes_for_url.merge(opts)) unless steps.empty?
304
+ end
305
+
306
+ def b64_data
307
+ "data:#{mime_type};base64,#{Base64.encode64(data)}"
308
+ end
309
+
310
+ # to_stuff...
311
+
312
+ def to_app
313
+ JobEndpoint.new(self)
314
+ end
315
+
316
+ def to_response(env={"REQUEST_METHOD" => "GET"})
317
+ to_app.call(env)
318
+ end
319
+
320
+ def to_path
321
+ "/#{serialize}"
322
+ end
323
+
324
+ def to_fetched_job(uid)
325
+ new_job = self.class.new(app, temp_object, meta)
326
+ new_job.fetch!(uid)
327
+ new_job.next_step_index = 1
328
+ new_job
329
+ end
330
+
331
+ # Step inspection
332
+
333
+ def fetch_step
334
+ last_step_of_type(Fetch)
335
+ end
336
+
337
+ def uid
338
+ step = fetch_step
339
+ step.uid if step
340
+ end
341
+
342
+ def uid_basename
343
+ File.basename(uid, '.*') if uid
344
+ end
345
+
346
+ def uid_extname
347
+ File.extname(uid) if uid
348
+ end
349
+
350
+ def generate_step
351
+ last_step_of_type(Generate)
352
+ end
353
+
354
+ def fetch_file_step
355
+ last_step_of_type(FetchFile)
356
+ end
357
+
358
+ def fetch_url_step
359
+ last_step_of_type(FetchUrl)
360
+ end
361
+
362
+ def process_steps
363
+ steps.select{|s| s.is_a?(Process) }
364
+ end
365
+
366
+ def encode_step
367
+ last_step_of_type(Encode)
368
+ end
369
+
370
+ def encoded_format
371
+ step = encode_step
372
+ step.format if step
373
+ end
374
+
375
+ def encoded_extname
376
+ format = encoded_format
377
+ ".#{format}" if format
378
+ end
379
+
380
+ # Misc
381
+
382
+ def store(opts={})
383
+ app.store(result, opts.merge(:meta => meta))
384
+ end
385
+
386
+ def inspect
387
+ to_s.sub(/>$/, " app=#{app}, steps=#{steps.inspect}, temp_object=#{temp_object.inspect}, steps applied:#{applied_steps.length}/#{steps.length} >")
388
+ end
389
+
390
+ # Name and stuff
391
+
392
+ attr_reader :meta
393
+
394
+ def meta=(hash)
395
+ raise ArgumentError, "meta must be a hash, you tried setting it as #{hash.inspect}" unless hash.is_a?(Hash)
396
+ @meta = hash
397
+ end
398
+
399
+ def name
400
+ meta[:name]
401
+ end
402
+
403
+ def name=(name)
404
+ meta[:name] = name
405
+ end
406
+
407
+ def basename
408
+ meta[:basename] || (File.basename(name, '.*') if name)
409
+ end
410
+
411
+ def ext
412
+ meta[:ext] || (File.extname(name)[/\.(.*)/, 1] if name)
413
+ end
414
+
415
+ def attributes_for_url
416
+ attrs = meta.reject{|k, v| !server.params_in_url.include?(k.to_s) }
417
+ attrs[:basename] ||= basename if server.params_in_url.include?('basename')
418
+ attrs[:ext] ||= ext if server.params_in_url.include?('ext')
419
+ attrs[:format] = (attrs[:format] || format_from_meta).to_s if server.params_in_url.include?('format')
420
+ attrs.delete_if{|k, v| v.blank? }
421
+ attrs
422
+ end
423
+
424
+ def update(content, meta)
425
+ if meta
426
+ meta.merge!(meta.delete(:meta)) if meta[:meta] # legacy data etc. may have nested meta hash - deprecate gracefully here
427
+ self.meta.merge!(meta)
428
+ end
429
+ if content
430
+ self.temp_object = TempObject.new(content)
431
+ self.name = temp_object.original_filename if name.nil? && temp_object.original_filename
432
+ end
433
+ end
434
+
435
+ protected
436
+
437
+ attr_writer :steps
438
+ attr_accessor :next_step_index
439
+
440
+ private
441
+
442
+ def format_from_meta
443
+ meta[:format] || (ext.to_sym if ext && app.trust_file_extensions)
444
+ end
445
+
446
+ def last_step_of_type(type)
447
+ steps.select{|s| s.is_a?(type) }.last
448
+ end
449
+
450
+ end
451
+ end