salebot_uploader 1.0.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.
Files changed (49) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +0 -0
  3. data/lib/generators/templates/uploader.rb.erb +9 -0
  4. data/lib/generators/uploader_generator.rb +7 -0
  5. data/lib/salebot_uploader/compatibility/paperclip.rb +104 -0
  6. data/lib/salebot_uploader/downloader/base.rb +101 -0
  7. data/lib/salebot_uploader/downloader/remote_file.rb +68 -0
  8. data/lib/salebot_uploader/error.rb +8 -0
  9. data/lib/salebot_uploader/locale/en.yml +17 -0
  10. data/lib/salebot_uploader/mount.rb +446 -0
  11. data/lib/salebot_uploader/mounter.rb +255 -0
  12. data/lib/salebot_uploader/orm/activerecord.rb +68 -0
  13. data/lib/salebot_uploader/processing/mini_magick.rb +194 -0
  14. data/lib/salebot_uploader/processing/rmagick.rb +402 -0
  15. data/lib/salebot_uploader/processing/vips.rb +284 -0
  16. data/lib/salebot_uploader/processing.rb +3 -0
  17. data/lib/salebot_uploader/sanitized_file.rb +357 -0
  18. data/lib/salebot_uploader/storage/abstract.rb +41 -0
  19. data/lib/salebot_uploader/storage/file.rb +124 -0
  20. data/lib/salebot_uploader/storage/fog.rb +547 -0
  21. data/lib/salebot_uploader/storage.rb +3 -0
  22. data/lib/salebot_uploader/test/matchers.rb +398 -0
  23. data/lib/salebot_uploader/uploader/cache.rb +223 -0
  24. data/lib/salebot_uploader/uploader/callbacks.rb +33 -0
  25. data/lib/salebot_uploader/uploader/configuration.rb +184 -0
  26. data/lib/salebot_uploader/uploader/content_type_allowlist.rb +61 -0
  27. data/lib/salebot_uploader/uploader/content_type_denylist.rb +62 -0
  28. data/lib/salebot_uploader/uploader/default_url.rb +17 -0
  29. data/lib/salebot_uploader/uploader/dimension.rb +66 -0
  30. data/lib/salebot_uploader/uploader/download.rb +24 -0
  31. data/lib/salebot_uploader/uploader/extension_allowlist.rb +63 -0
  32. data/lib/salebot_uploader/uploader/extension_denylist.rb +64 -0
  33. data/lib/salebot_uploader/uploader/file_size.rb +43 -0
  34. data/lib/salebot_uploader/uploader/mountable.rb +44 -0
  35. data/lib/salebot_uploader/uploader/processing.rb +125 -0
  36. data/lib/salebot_uploader/uploader/proxy.rb +99 -0
  37. data/lib/salebot_uploader/uploader/remove.rb +21 -0
  38. data/lib/salebot_uploader/uploader/serialization.rb +28 -0
  39. data/lib/salebot_uploader/uploader/store.rb +142 -0
  40. data/lib/salebot_uploader/uploader/url.rb +44 -0
  41. data/lib/salebot_uploader/uploader/versions.rb +350 -0
  42. data/lib/salebot_uploader/uploader.rb +53 -0
  43. data/lib/salebot_uploader/utilities/file_name.rb +47 -0
  44. data/lib/salebot_uploader/utilities/uri.rb +26 -0
  45. data/lib/salebot_uploader/utilities.rb +7 -0
  46. data/lib/salebot_uploader/validations/active_model.rb +76 -0
  47. data/lib/salebot_uploader/version.rb +3 -0
  48. data/lib/salebot_uploader.rb +62 -0
  49. metadata +392 -0
@@ -0,0 +1,68 @@
1
+ require 'active_record'
2
+ require 'salebot_uploader/validations/active_model'
3
+
4
+ module SalebotUploader
5
+ module ActiveRecord
6
+
7
+ include SalebotUploader::Mount
8
+
9
+ private
10
+
11
+ def mount_base(column, uploader=nil, options={}, &block)
12
+ super
13
+
14
+ alias_method :read_uploader, :read_attribute
15
+ alias_method :write_uploader, :write_attribute
16
+ public :read_uploader
17
+ public :write_uploader
18
+
19
+ include SalebotUploader::Validations::ActiveModel
20
+
21
+ validates_integrity_of column if uploader_option(column.to_sym, :validate_integrity)
22
+ validates_processing_of column if uploader_option(column.to_sym, :validate_processing)
23
+ validates_download_of column if uploader_option(column.to_sym, :validate_download)
24
+
25
+ after_save :"store_#{column}!"
26
+ before_save :"write_#{column}_identifier"
27
+ if ::ActiveRecord.try(:run_after_transaction_callbacks_in_order_defined)
28
+ after_commit :"remove_previously_stored_#{column}", :on => :update
29
+ after_commit :"reset_previous_changes_for_#{column}"
30
+ after_commit :"mark_remove_#{column}_false", :on => :update
31
+ else
32
+ after_commit :"mark_remove_#{column}_false", :on => :update
33
+ after_commit :"reset_previous_changes_for_#{column}"
34
+ after_commit :"remove_previously_stored_#{column}", :on => :update
35
+ end
36
+ after_commit :"remove_#{column}!", :on => :destroy
37
+ after_rollback :"remove_rolled_back_#{column}"
38
+
39
+ mod = Module.new
40
+ prepend mod
41
+ mod.class_eval <<-RUBY, __FILE__, __LINE__+1
42
+ # Reset cached mounter on record reload
43
+ def reload(*)
44
+ @_mounters = nil
45
+ super
46
+ end
47
+
48
+ # Reset cached mounter on record dup
49
+ def initialize_dup(other)
50
+ old_uploaders = _mounter(:"#{column}").uploaders
51
+ super
52
+ @_mounters[:"#{column}"] = nil
53
+ # The attribute needs to be cleared to prevent it from picked up as identifier
54
+ write_attribute(_mounter(:#{column}).serialization_column, nil)
55
+ _mounter(:"#{column}").cache(old_uploaders)
56
+ end
57
+
58
+ def write_#{column}_identifier
59
+ return unless has_attribute?(_mounter(:#{column}).serialization_column)
60
+ super
61
+ end
62
+ RUBY
63
+ end
64
+
65
+ end # ActiveRecord
66
+ end # SalebotUploader
67
+
68
+ ActiveRecord::Base.extend SalebotUploader::ActiveRecord
@@ -0,0 +1,194 @@
1
+ module SalebotUploader
2
+ module MiniMagick
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ require "image_processing/mini_magick"
7
+ end
8
+
9
+ module ClassMethods
10
+ def convert(format)
11
+ process :convert => format
12
+ end
13
+
14
+ def resize_to_limit(width, height)
15
+ process :resize_to_limit => [width, height]
16
+ end
17
+
18
+ def resize_to_fit(width, height)
19
+ process :resize_to_fit => [width, height]
20
+ end
21
+
22
+ def resize_to_fill(width, height, gravity='Center')
23
+ process :resize_to_fill => [width, height, gravity]
24
+ end
25
+
26
+ def resize_and_pad(width, height, background=:transparent, gravity='Center')
27
+ process :resize_and_pad => [width, height, background, gravity]
28
+ end
29
+ end
30
+ def convert(format, page=nil, &block)
31
+ minimagick!(block) do |builder|
32
+ builder = builder.convert(format)
33
+ builder = builder.loader(page: page) if page
34
+ builder
35
+ end
36
+ end
37
+
38
+ def resize_to_limit(width, height, combine_options: {}, &block)
39
+ width, height = resolve_dimensions(width, height)
40
+
41
+ minimagick!(block) do |builder|
42
+ builder.resize_to_limit(width, height)
43
+ .apply(combine_options)
44
+ end
45
+ end
46
+
47
+ def resize_to_fit(width, height, combine_options: {}, &block)
48
+ width, height = resolve_dimensions(width, height)
49
+
50
+ minimagick!(block) do |builder|
51
+ builder.resize_to_fit(width, height)
52
+ .apply(combine_options)
53
+ end
54
+ end
55
+
56
+ def resize_to_fill(width, height, gravity = 'Center', combine_options: {}, &block)
57
+ width, height = resolve_dimensions(width, height)
58
+
59
+ minimagick!(block) do |builder|
60
+ builder.resize_to_fill(width, height, gravity: gravity)
61
+ .apply(combine_options)
62
+ end
63
+ end
64
+
65
+ def resize_and_pad(width, height, background=:transparent, gravity='Center', combine_options: {}, &block)
66
+ width, height = resolve_dimensions(width, height)
67
+
68
+ minimagick!(block) do |builder|
69
+ builder.resize_and_pad(width, height, background: background, gravity: gravity)
70
+ .apply(combine_options)
71
+ end
72
+ end
73
+
74
+ ##
75
+ # Returns the width of the image in pixels.
76
+ #
77
+ # === Returns
78
+ #
79
+ # [Integer] the image's width in pixels
80
+ #
81
+ def width
82
+ mini_magick_image[:width]
83
+ end
84
+
85
+ ##
86
+ # Returns the height of the image in pixels.
87
+ #
88
+ # === Returns
89
+ #
90
+ # [Integer] the image's height in pixels
91
+ #
92
+ def height
93
+ mini_magick_image[:height]
94
+ end
95
+
96
+ ##
97
+ # Manipulate the image with MiniMagick. This method will load up an image
98
+ # and then pass each of its frames to the supplied block. It will then
99
+ # save the image to disk.
100
+ #
101
+ # NOTE: This method exists mostly for backwards compatibility, you should
102
+ # probably use #minimagick!.
103
+ #
104
+ # === Gotcha
105
+ #
106
+ # This method assumes that the object responds to +current_path+.
107
+ # Any class that this module is mixed into must have a +current_path+ method.
108
+ # SalebotUploader::Uploader does, so you won't need to worry about this in
109
+ # most cases.
110
+ #
111
+ # === Yields
112
+ #
113
+ # [MiniMagick::Image] manipulations to perform
114
+ #
115
+ # === Raises
116
+ #
117
+ # [SalebotUploader::ProcessingError] if manipulation failed.
118
+ #
119
+ def manipulate!
120
+ cache_stored_file! if !cached?
121
+ image = ::MiniMagick::Image.open(current_path)
122
+
123
+ image = yield(image)
124
+ FileUtils.mv image.path, current_path
125
+
126
+ image.run_command("identify", current_path)
127
+ rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
128
+ raise e if e.message =~ /(You must have .+ installed|is not installed|executable not found)/
129
+ message = I18n.translate(:"errors.messages.processing_error")
130
+ raise SalebotUploader::ProcessingError, message
131
+ ensure
132
+ image.destroy! if image
133
+ end
134
+
135
+ # Process the image with MiniMagick, using the ImageProcessing gem. This
136
+ # method will build a "convert" ImageMagick command and execute it on the
137
+ # current image.
138
+ #
139
+ # === Gotcha
140
+ #
141
+ # This method assumes that the object responds to +current_path+.
142
+ # Any class that this module is mixed into must have a +current_path+ method.
143
+ # SalebotUploader::Uploader does, so you won't need to worry about this in
144
+ # most cases.
145
+ #
146
+ # === Yields
147
+ #
148
+ # [ImageProcessing::Builder] use it to define processing to be performed
149
+ #
150
+ # === Raises
151
+ #
152
+ # [SalebotUploader::ProcessingError] if processing failed.
153
+ def minimagick!(block = nil)
154
+ builder = ImageProcessing::MiniMagick.source(current_path)
155
+ builder = yield(builder)
156
+
157
+ result = builder.call
158
+ result.close
159
+
160
+ # backwards compatibility (we want to eventually move away from MiniMagick::Image)
161
+ if block
162
+ image = ::MiniMagick::Image.new(result.path, result)
163
+ image = block.call(image)
164
+ result = image.instance_variable_get(:@tempfile)
165
+ end
166
+
167
+ FileUtils.mv result.path, current_path
168
+
169
+ if File.extname(result.path) != File.extname(current_path)
170
+ move_to = current_path.chomp(File.extname(current_path)) + File.extname(result.path)
171
+ file.content_type = Marcel::Magic.by_path(move_to).try(:type)
172
+ file.move_to(move_to, permissions, directory_permissions)
173
+ end
174
+ rescue ::MiniMagick::Error, ::MiniMagick::Invalid => e
175
+ raise e if e.message =~ /(You must have .+ installed|is not installed|executable not found)/
176
+ message = I18n.translate(:"errors.messages.processing_error")
177
+ raise SalebotUploader::ProcessingError, message
178
+ end
179
+
180
+ private
181
+
182
+ def resolve_dimensions(*dimensions)
183
+ dimensions.map do |value|
184
+ next value unless value.instance_of?(Proc)
185
+ value.arity >= 1 ? value.call(self) : value.call
186
+ end
187
+ end
188
+
189
+ def mini_magick_image
190
+ ::MiniMagick::Image.read(read)
191
+ end
192
+
193
+ end # MiniMagick
194
+ end # SalebotUploader
@@ -0,0 +1,402 @@
1
+ module SalebotUploader
2
+
3
+ ##
4
+ # This module simplifies manipulation with RMagick by providing a set
5
+ # of convenient helper methods. If you want to use them, you'll need to
6
+ # require this file:
7
+ #
8
+ # require 'SalebotUploader/processing/rmagick'
9
+ #
10
+ # And then include it in your uploader:
11
+ #
12
+ # class MyUploader < SalebotUploader::Uploader::Base
13
+ # include SalebotUploader::RMagick
14
+ # end
15
+ #
16
+ # You can now use the provided helpers:
17
+ #
18
+ # class MyUploader < SalebotUploader::Uploader::Base
19
+ # include SalebotUploader::RMagick
20
+ #
21
+ # process :resize_to_fit => [200, 200]
22
+ # end
23
+ #
24
+ # Or create your own helpers with the powerful manipulate! method. Check
25
+ # out the RMagick docs at http://www.imagemagick.org/RMagick/doc/ for more
26
+ # info
27
+ #
28
+ # class MyUploader < SalebotUploader::Uploader::Base
29
+ # include SalebotUploader::RMagick
30
+ #
31
+ # process :do_stuff => 10.0
32
+ #
33
+ # def do_stuff(blur_factor)
34
+ # manipulate! do |img|
35
+ # img = img.sepiatone
36
+ # img = img.auto_orient
37
+ # img = img.radial_blur(blur_factor)
38
+ # end
39
+ # end
40
+ # end
41
+ #
42
+ # === Note
43
+ #
44
+ # You should be aware how RMagick handles memory. manipulate! takes care
45
+ # of freeing up memory for you, but for optimum memory usage you should
46
+ # use destructive operations as much as possible:
47
+ #
48
+ # DON'T DO THIS:
49
+ # img = img.resize_to_fit
50
+ #
51
+ # DO THIS INSTEAD:
52
+ # img.resize_to_fit!
53
+ #
54
+ # Read this for more information why:
55
+ #
56
+ # http://rubyforge.org/forum/forum.php?thread_id=1374&forum_id=1618
57
+ #
58
+ module RMagick
59
+ extend ActiveSupport::Concern
60
+
61
+ included do
62
+ begin
63
+ require "rmagick"
64
+ rescue LoadError
65
+ begin
66
+ require "RMagick"
67
+ rescue LoadError => e
68
+ e.message << " (You may need to install the rmagick gem)"
69
+ raise e
70
+ end
71
+ end
72
+
73
+ prepend Module.new {
74
+ def initialize(*)
75
+ super
76
+ @format = nil
77
+ end
78
+ }
79
+ end
80
+
81
+ module ClassMethods
82
+ def convert(format)
83
+ process :convert => format
84
+ end
85
+
86
+ def resize_to_limit(width, height)
87
+ process :resize_to_limit => [width, height]
88
+ end
89
+
90
+ def resize_to_fit(width, height)
91
+ process :resize_to_fit => [width, height]
92
+ end
93
+
94
+ def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
95
+ process :resize_to_fill => [width, height, gravity]
96
+ end
97
+
98
+ def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
99
+ process :resize_and_pad => [width, height, background, gravity]
100
+ end
101
+
102
+ def resize_to_geometry_string(geometry_string)
103
+ process :resize_to_geometry_string => [geometry_string]
104
+ end
105
+ end
106
+
107
+ ##
108
+ # Changes the image encoding format to the given format
109
+ #
110
+ # See even http://www.imagemagick.org/RMagick/doc/magick.html#formats
111
+ #
112
+ # === Parameters
113
+ #
114
+ # [format (#to_s)] an abbreviation of the format
115
+ #
116
+ # === Yields
117
+ #
118
+ # [Magick::Image] additional manipulations to perform
119
+ #
120
+ # === Examples
121
+ #
122
+ # image.convert(:png)
123
+ #
124
+ def convert(format)
125
+ manipulate!(:format => format)
126
+ @format = format
127
+ end
128
+
129
+ ##
130
+ # Resize the image to fit within the specified dimensions while retaining
131
+ # the original aspect ratio. Will only resize the image if it is larger than the
132
+ # specified dimensions. The resulting image may be shorter or narrower than specified
133
+ # in the smaller dimension but will not be larger than the specified values.
134
+ #
135
+ # === Parameters
136
+ #
137
+ # [width (Integer)] the width to scale the image to
138
+ # [height (Integer)] the height to scale the image to
139
+ #
140
+ # === Yields
141
+ #
142
+ # [Magick::Image] additional manipulations to perform
143
+ #
144
+ def resize_to_limit(width, height)
145
+ width = dimension_from width
146
+ height = dimension_from height
147
+ manipulate! do |img|
148
+ geometry = Magick::Geometry.new(width, height, 0, 0, Magick::GreaterGeometry)
149
+ new_img = img.change_geometry(geometry) do |new_width, new_height|
150
+ img.resize(new_width, new_height)
151
+ end
152
+ destroy_image(img)
153
+ new_img = yield(new_img) if block_given?
154
+ new_img
155
+ end
156
+ end
157
+
158
+ ##
159
+ # From the RMagick documentation: "Resize the image to fit within the
160
+ # specified dimensions while retaining the original aspect ratio. The
161
+ # image may be shorter or narrower than specified in the smaller dimension
162
+ # but will not be larger than the specified values."
163
+ #
164
+ # See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fit
165
+ #
166
+ # === Parameters
167
+ #
168
+ # [width (Integer)] the width to scale the image to
169
+ # [height (Integer)] the height to scale the image to
170
+ #
171
+ # === Yields
172
+ #
173
+ # [Magick::Image] additional manipulations to perform
174
+ #
175
+ def resize_to_fit(width, height)
176
+ width = dimension_from width
177
+ height = dimension_from height
178
+ manipulate! do |img|
179
+ img.resize_to_fit!(width, height)
180
+ img = yield(img) if block_given?
181
+ img
182
+ end
183
+ end
184
+
185
+ ##
186
+ # From the RMagick documentation: "Resize the image to fit within the
187
+ # specified dimensions while retaining the aspect ratio of the original
188
+ # image. If necessary, crop the image in the larger dimension."
189
+ #
190
+ # See even http://www.imagemagick.org/RMagick/doc/image3.html#resize_to_fill
191
+ #
192
+ # === Parameters
193
+ #
194
+ # [width (Integer)] the width to scale the image to
195
+ # [height (Integer)] the height to scale the image to
196
+ #
197
+ # === Yields
198
+ #
199
+ # [Magick::Image] additional manipulations to perform
200
+ #
201
+ def resize_to_fill(width, height, gravity=::Magick::CenterGravity)
202
+ width = dimension_from width
203
+ height = dimension_from height
204
+ manipulate! do |img|
205
+ img.crop_resized!(width, height, gravity)
206
+ img = yield(img) if block_given?
207
+ img
208
+ end
209
+ end
210
+
211
+ ##
212
+ # Resize the image to fit within the specified dimensions while retaining
213
+ # the original aspect ratio. If necessary, will pad the remaining area
214
+ # with the given color, which defaults to transparent (for gif and png,
215
+ # white for jpeg).
216
+ #
217
+ # === Parameters
218
+ #
219
+ # [width (Integer)] the width to scale the image to
220
+ # [height (Integer)] the height to scale the image to
221
+ # [background (String, :transparent)] the color of the background as a hexcode, like "#ff45de"
222
+ # [gravity (Magick::GravityType)] how to position the image
223
+ #
224
+ # === Yields
225
+ #
226
+ # [Magick::Image] additional manipulations to perform
227
+ #
228
+ def resize_and_pad(width, height, background=:transparent, gravity=::Magick::CenterGravity)
229
+ width = dimension_from width
230
+ height = dimension_from height
231
+ manipulate! do |img|
232
+ img.resize_to_fit!(width, height)
233
+ filled = ::Magick::Image.new(width, height) { |image| image.background_color = background == :transparent ? 'rgba(255,255,255,0)' : background.to_s }
234
+ filled.composite!(img, gravity, ::Magick::OverCompositeOp)
235
+ destroy_image(img)
236
+ filled = yield(filled) if block_given?
237
+ filled
238
+ end
239
+ end
240
+
241
+ ##
242
+ # Resize the image per the provided geometry string.
243
+ #
244
+ # === Parameters
245
+ #
246
+ # [geometry_string (String)] the proportions in which to scale image
247
+ #
248
+ # === Yields
249
+ #
250
+ # [Magick::Image] additional manipulations to perform
251
+ #
252
+ def resize_to_geometry_string(geometry_string)
253
+ manipulate! do |img|
254
+ new_img = img.change_geometry(geometry_string) do |new_width, new_height|
255
+ img.resize(new_width, new_height)
256
+ end
257
+ destroy_image(img)
258
+ new_img = yield(new_img) if block_given?
259
+ new_img
260
+ end
261
+ end
262
+
263
+ ##
264
+ # Returns the width of the image.
265
+ #
266
+ # === Returns
267
+ #
268
+ # [Integer] the image's width in pixels
269
+ #
270
+ def width
271
+ rmagick_image.columns
272
+ end
273
+
274
+ ##
275
+ # Returns the height of the image.
276
+ #
277
+ # === Returns
278
+ #
279
+ # [Integer] the image's height in pixels
280
+ #
281
+ def height
282
+ rmagick_image.rows
283
+ end
284
+
285
+ ##
286
+ # Manipulate the image with RMagick. This method will load up an image
287
+ # and then pass each of its frames to the supplied block. It will then
288
+ # save the image to disk.
289
+ #
290
+ # === Gotcha
291
+ #
292
+ # This method assumes that the object responds to +current_path+.
293
+ # Any class that this module is mixed into must have a +current_path+ method.
294
+ # SalebotUploader::Uploader does, so you won't need to worry about this in
295
+ # most cases.
296
+ #
297
+ # === Yields
298
+ #
299
+ # [Magick::Image] manipulations to perform
300
+ # [Integer] Frame index if the image contains multiple frames
301
+ # [Hash] options, see below
302
+ #
303
+ # === Options
304
+ #
305
+ # The options argument to this method is also yielded as the third
306
+ # block argument.
307
+ #
308
+ # Currently, the following options are defined:
309
+ #
310
+ # ==== :write
311
+ # A hash of assignments to be evaluated in the block given to the RMagick write call.
312
+ #
313
+ # An example:
314
+ #
315
+ # manipulate! do |img, index, options|
316
+ # options[:write] = {
317
+ # :quality => 50,
318
+ # :depth => 8
319
+ # }
320
+ # img
321
+ # end
322
+ #
323
+ # This will translate to the following RMagick::Image#write call:
324
+ #
325
+ # image.write do |img|
326
+ # self.quality = 50
327
+ # self.depth = 8
328
+ # end
329
+ #
330
+ # ==== :read
331
+ # A hash of assignments to be given to the RMagick read call.
332
+ #
333
+ # The options available are identical to those for write, but are passed in directly, like this:
334
+ #
335
+ # manipulate! :read => { :density => 300 }
336
+ #
337
+ # ==== :format
338
+ # Specify the output format. If unset, the filename extension is used to determine the format.
339
+ #
340
+ # === Raises
341
+ #
342
+ # [SalebotUploader::ProcessingError] if manipulation failed.
343
+ #
344
+ def manipulate!(options={}, &block)
345
+ cache_stored_file! if !cached?
346
+
347
+ read_block = create_info_block(options[:read])
348
+ image = ::Magick::Image.read(current_path, &read_block)
349
+ frames = ::Magick::ImageList.new
350
+
351
+ image.each_with_index do |frame, index|
352
+ frame = yield(*[frame, index, options].take(block.arity)) if block_given?
353
+ frames << frame if frame
354
+ end
355
+ frames.append(true) if block_given?
356
+
357
+ write_block = create_info_block(options[:write])
358
+
359
+ if options[:format] || @format
360
+ frames.write("#{options[:format] || @format}:#{current_path}", &write_block)
361
+ move_to = current_path.chomp(File.extname(current_path)) + ".#{options[:format] || @format}"
362
+ file.content_type = Marcel::Magic.by_path(move_to).try(:type)
363
+ file.move_to(move_to, permissions, directory_permissions)
364
+ else
365
+ frames.write(current_path, &write_block)
366
+ end
367
+
368
+ destroy_image(frames)
369
+ rescue ::Magick::ImageMagickError
370
+ raise SalebotUploader::ProcessingError, I18n.translate(:"errors.messages.processing_error")
371
+ end
372
+
373
+ private
374
+
375
+ def create_info_block(options)
376
+ return nil unless options
377
+ proc do |img|
378
+ options.each do |k, v|
379
+ if v.is_a?(String) && (matches = v.match(/^["'](.+)["']/))
380
+ ActiveSupport::Deprecation.warn "Passing quoted strings like #{v} to #manipulate! is deprecated, pass them without quoting."
381
+ v = matches[1]
382
+ end
383
+ img.public_send(:"#{k}=", v)
384
+ end
385
+ end
386
+ end
387
+
388
+ def destroy_image(image)
389
+ image.try(:destroy!)
390
+ end
391
+
392
+ def dimension_from(value)
393
+ return value unless value.instance_of?(Proc)
394
+ value.arity >= 1 ? value.call(self) : value.call
395
+ end
396
+
397
+ def rmagick_image
398
+ ::Magick::Image.from_blob(self.read).first
399
+ end
400
+
401
+ end # RMagick
402
+ end # SalebotUploader