fog-dragonfly 0.8.1

Sign up to get free protection for your applications and to get access to all the features.

Potentially problematic release.


This version of fog-dragonfly might be problematic. Click here for more details.

Files changed (155) hide show
  1. data/.specopts +2 -0
  2. data/.yardopts +23 -0
  3. data/Gemfile +23 -0
  4. data/Gemfile.rails.2.3.5 +14 -0
  5. data/History.md +266 -0
  6. data/LICENSE +20 -0
  7. data/README.md +88 -0
  8. data/Rakefile +92 -0
  9. data/VERSION +1 -0
  10. data/config.ru +13 -0
  11. data/docs.watchr +1 -0
  12. data/dragonfly.gemspec +293 -0
  13. data/extra_docs/Analysers.md +108 -0
  14. data/extra_docs/Caching.md +23 -0
  15. data/extra_docs/Configuration.md +138 -0
  16. data/extra_docs/DataStorage.md +136 -0
  17. data/extra_docs/Encoding.md +96 -0
  18. data/extra_docs/GeneralUsage.md +121 -0
  19. data/extra_docs/Generators.md +102 -0
  20. data/extra_docs/Heroku.md +50 -0
  21. data/extra_docs/Index.md +36 -0
  22. data/extra_docs/MimeTypes.md +40 -0
  23. data/extra_docs/Models.md +266 -0
  24. data/extra_docs/Mongo.md +45 -0
  25. data/extra_docs/Processing.md +130 -0
  26. data/extra_docs/Rack.md +52 -0
  27. data/extra_docs/Rails2.md +55 -0
  28. data/extra_docs/Rails3.md +62 -0
  29. data/extra_docs/Sinatra.md +25 -0
  30. data/extra_docs/URLs.md +169 -0
  31. data/features/3.0.3.feature +8 -0
  32. data/features/images.feature +47 -0
  33. data/features/no_processing.feature +14 -0
  34. data/features/rails_2.3.5.feature +7 -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 +39 -0
  38. data/features/support/env.rb +40 -0
  39. data/fixtures/files/app/models/album.rb +3 -0
  40. data/fixtures/files/app/views/albums/new.html.erb +4 -0
  41. data/fixtures/files/app/views/albums/show.html.erb +4 -0
  42. data/fixtures/files/config/initializers/dragonfly.rb +4 -0
  43. data/fixtures/files/features/manage_album_images.feature +12 -0
  44. data/fixtures/files/features/step_definitions/image_steps.rb +15 -0
  45. data/fixtures/files/features/support/paths.rb +15 -0
  46. data/fixtures/files/features/text_images.feature +7 -0
  47. data/fixtures/rails_2.3.5/template.rb +10 -0
  48. data/fixtures/rails_3.0.3/template.rb +20 -0
  49. data/irbrc.rb +17 -0
  50. data/lib/dragonfly.rb +45 -0
  51. data/lib/dragonfly/active_model_extensions.rb +13 -0
  52. data/lib/dragonfly/active_model_extensions/attachment.rb +169 -0
  53. data/lib/dragonfly/active_model_extensions/class_methods.rb +45 -0
  54. data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
  55. data/lib/dragonfly/active_model_extensions/validations.rb +37 -0
  56. data/lib/dragonfly/analyser.rb +59 -0
  57. data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
  58. data/lib/dragonfly/analysis/image_magick_analyser.rb +47 -0
  59. data/lib/dragonfly/analysis/r_magick_analyser.rb +63 -0
  60. data/lib/dragonfly/app.rb +182 -0
  61. data/lib/dragonfly/config/heroku.rb +19 -0
  62. data/lib/dragonfly/config/image_magick.rb +41 -0
  63. data/lib/dragonfly/config/r_magick.rb +46 -0
  64. data/lib/dragonfly/config/rails.rb +17 -0
  65. data/lib/dragonfly/configurable.rb +119 -0
  66. data/lib/dragonfly/core_ext/object.rb +8 -0
  67. data/lib/dragonfly/core_ext/string.rb +9 -0
  68. data/lib/dragonfly/core_ext/symbol.rb +9 -0
  69. data/lib/dragonfly/data_storage.rb +9 -0
  70. data/lib/dragonfly/data_storage/file_data_store.rb +114 -0
  71. data/lib/dragonfly/data_storage/mongo_data_store.rb +82 -0
  72. data/lib/dragonfly/data_storage/s3data_store.rb +115 -0
  73. data/lib/dragonfly/encoder.rb +13 -0
  74. data/lib/dragonfly/encoding/image_magick_encoder.rb +57 -0
  75. data/lib/dragonfly/encoding/r_magick_encoder.rb +61 -0
  76. data/lib/dragonfly/function_manager.rb +69 -0
  77. data/lib/dragonfly/generation/hash_with_css_style_keys.rb +23 -0
  78. data/lib/dragonfly/generation/image_magick_generator.rb +140 -0
  79. data/lib/dragonfly/generation/r_magick_generator.rb +155 -0
  80. data/lib/dragonfly/generator.rb +9 -0
  81. data/lib/dragonfly/image_magick_utils.rb +81 -0
  82. data/lib/dragonfly/job.rb +371 -0
  83. data/lib/dragonfly/job_builder.rb +39 -0
  84. data/lib/dragonfly/job_definitions.rb +26 -0
  85. data/lib/dragonfly/job_endpoint.rb +15 -0
  86. data/lib/dragonfly/loggable.rb +28 -0
  87. data/lib/dragonfly/middleware.rb +34 -0
  88. data/lib/dragonfly/processing/image_magick_processor.rb +99 -0
  89. data/lib/dragonfly/processing/r_magick_processor.rb +126 -0
  90. data/lib/dragonfly/processor.rb +9 -0
  91. data/lib/dragonfly/r_magick_utils.rb +48 -0
  92. data/lib/dragonfly/rails/images.rb +22 -0
  93. data/lib/dragonfly/response.rb +82 -0
  94. data/lib/dragonfly/routed_endpoint.rb +40 -0
  95. data/lib/dragonfly/serializer.rb +32 -0
  96. data/lib/dragonfly/simple_cache.rb +23 -0
  97. data/lib/dragonfly/simple_endpoint.rb +63 -0
  98. data/lib/dragonfly/temp_object.rb +220 -0
  99. data/samples/beach.png +0 -0
  100. data/samples/egg.png +0 -0
  101. data/samples/round.gif +0 -0
  102. data/samples/sample.docx +0 -0
  103. data/samples/taj.jpg +0 -0
  104. data/spec/argument_matchers.rb +19 -0
  105. data/spec/dragonfly/active_model_extensions/active_model_setup.rb +97 -0
  106. data/spec/dragonfly/active_model_extensions/active_record_setup.rb +85 -0
  107. data/spec/dragonfly/active_model_extensions/model_spec.rb +723 -0
  108. data/spec/dragonfly/active_model_extensions/spec_helper.rb +11 -0
  109. data/spec/dragonfly/analyser_spec.rb +123 -0
  110. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +57 -0
  111. data/spec/dragonfly/analysis/image_magick_analyser_spec.rb +15 -0
  112. data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +27 -0
  113. data/spec/dragonfly/analysis/shared_analyser_spec.rb +51 -0
  114. data/spec/dragonfly/app_spec.rb +280 -0
  115. data/spec/dragonfly/config/r_magick_spec.rb +25 -0
  116. data/spec/dragonfly/configurable_spec.rb +220 -0
  117. data/spec/dragonfly/core_ext/string_spec.rb +17 -0
  118. data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
  119. data/spec/dragonfly/data_storage/data_store_spec.rb +76 -0
  120. data/spec/dragonfly/data_storage/file_data_store_spec.rb +169 -0
  121. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +38 -0
  122. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +94 -0
  123. data/spec/dragonfly/deprecation_spec.rb +20 -0
  124. data/spec/dragonfly/encoding/image_magick_encoder_spec.rb +41 -0
  125. data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +37 -0
  126. data/spec/dragonfly/function_manager_spec.rb +154 -0
  127. data/spec/dragonfly/generation/hash_with_css_style_keys_spec.rb +24 -0
  128. data/spec/dragonfly/generation/image_magick_generator_spec.rb +12 -0
  129. data/spec/dragonfly/generation/r_magick_generator_spec.rb +24 -0
  130. data/spec/dragonfly/generation/shared_generator_spec.rb +91 -0
  131. data/spec/dragonfly/image_magick_utils_spec.rb +16 -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 +120 -0
  135. data/spec/dragonfly/job_spec.rb +773 -0
  136. data/spec/dragonfly/loggable_spec.rb +80 -0
  137. data/spec/dragonfly/middleware_spec.rb +68 -0
  138. data/spec/dragonfly/processing/image_magick_processor_spec.rb +29 -0
  139. data/spec/dragonfly/processing/r_magick_processor_spec.rb +26 -0
  140. data/spec/dragonfly/processing/shared_processing_spec.rb +215 -0
  141. data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
  142. data/spec/dragonfly/serializer_spec.rb +61 -0
  143. data/spec/dragonfly/simple_cache_spec.rb +27 -0
  144. data/spec/dragonfly/simple_endpoint_spec.rb +89 -0
  145. data/spec/dragonfly/temp_object_spec.rb +352 -0
  146. data/spec/image_matchers.rb +47 -0
  147. data/spec/simple_matchers.rb +44 -0
  148. data/spec/spec_helper.rb +58 -0
  149. data/yard/handlers/configurable_attr_handler.rb +38 -0
  150. data/yard/setup.rb +15 -0
  151. data/yard/templates/default/fulldoc/html/css/common.css +107 -0
  152. data/yard/templates/default/layout/html/layout.erb +87 -0
  153. data/yard/templates/default/module/html/configuration_summary.erb +31 -0
  154. data/yard/templates/default/module/setup.rb +17 -0
  155. metadata +550 -0
@@ -0,0 +1,155 @@
1
+ require 'RMagick'
2
+
3
+ module Dragonfly
4
+ module Generation
5
+ class RMagickGenerator
6
+
7
+ FONT_STYLES = {
8
+ 'normal' => Magick::NormalStyle,
9
+ 'italic' => Magick::ItalicStyle,
10
+ 'oblique' => Magick::ObliqueStyle
11
+ }
12
+
13
+ FONT_STRETCHES = {
14
+ 'normal' => Magick::NormalStretch,
15
+ 'semi-condensed' => Magick::SemiCondensedStretch,
16
+ 'condensed' => Magick::CondensedStretch,
17
+ 'extra-condensed' => Magick::ExtraCondensedStretch,
18
+ 'ultra-condensed' => Magick::UltraCondensedStretch,
19
+ 'semi-expanded' => Magick::SemiExpandedStretch,
20
+ 'expanded' => Magick::ExpandedStretch,
21
+ 'extra-expanded' => Magick::ExtraExpandedStretch,
22
+ 'ultra-expanded' => Magick::UltraExpandedStretch
23
+ }
24
+
25
+ FONT_WEIGHTS = {
26
+ 'normal' => Magick::NormalWeight,
27
+ 'bold' => Magick::BoldWeight,
28
+ 'bolder' => Magick::BolderWeight,
29
+ 'lighter' => Magick::LighterWeight,
30
+ '100' => 100,
31
+ '200' => 200,
32
+ '300' => 300,
33
+ '400' => 400,
34
+ '500' => 500,
35
+ '600' => 600,
36
+ '700' => 700,
37
+ '800' => 800,
38
+ '900' => 900
39
+ }
40
+
41
+ include RMagickUtils
42
+ include Configurable
43
+ configurable_attr :use_filesystem, true
44
+
45
+ def plasma(width, height, format='png')
46
+ image = Magick::Image.read("plasma:fractal"){self.size = "#{width}x#{height}"}.first
47
+ image.format = format.to_s
48
+ content = use_filesystem ? write_to_tempfile(image) : image.to_blob
49
+ image.destroy!
50
+ [
51
+ content,
52
+ {:format => format.to_sym, :name => "plasma.#{format}"}
53
+ ]
54
+ end
55
+
56
+ def text(text_string, opts={})
57
+ opts = HashWithCssStyleKeys[opts]
58
+
59
+ draw = Magick::Draw.new
60
+ draw.gravity = Magick::CenterGravity
61
+ draw.text_antialias = true
62
+
63
+ # Font size
64
+ font_size = (opts[:font_size] || 12).to_i
65
+
66
+ # Scale up the text for better quality -
67
+ # it will be reshrunk at the end
68
+ s = scale_factor_for(font_size)
69
+
70
+ # Settings
71
+ draw.pointsize = font_size * s
72
+ draw.font = opts[:font] if opts[:font]
73
+ draw.font_family = opts[:font_family] if opts[:font_family]
74
+ draw.fill = opts[:color] if opts[:color]
75
+ draw.stroke = opts[:stroke_color] if opts[:stroke_color]
76
+ draw.font_style = FONT_STYLES[opts[:font_style]] if opts[:font_style]
77
+ draw.font_stretch = FONT_STRETCHES[opts[:font_stretch]] if opts[:font_stretch]
78
+ draw.font_weight = FONT_WEIGHTS[opts[:font_weight]] if opts[:font_weight]
79
+
80
+ # Padding
81
+ # NB the values are scaled up by the scale factor
82
+ pt, pr, pb, pl = parse_padding_string(opts[:padding]) if opts[:padding]
83
+ padding_top = (opts[:padding_top] || pt || 0) * s
84
+ padding_right = (opts[:padding_right] || pr || 0) * s
85
+ padding_bottom = (opts[:padding_bottom] || pb || 0) * s
86
+ padding_left = (opts[:padding_left] || pl || 0) * s
87
+
88
+ # Calculate (scaled up) dimensions
89
+ metrics = draw.get_type_metrics(text_string)
90
+ width, height = metrics.width, metrics.height
91
+
92
+ scaled_up_width = padding_left + width + padding_right
93
+ scaled_up_height = padding_top + height + padding_bottom
94
+
95
+ # Draw the background
96
+ image = Magick::Image.new(scaled_up_width, scaled_up_height){
97
+ self.background_color = opts[:background_color] || 'transparent'
98
+ }
99
+ # Draw the text
100
+ draw.annotate(image, width, height, padding_left, padding_top, text_string)
101
+
102
+ # Scale back down again
103
+ image.scale!(1/s)
104
+
105
+ format = opts[:format] || :png
106
+ image.format = format.to_s
107
+
108
+ # Output image either as a string or a tempfile
109
+ content = use_filesystem ? write_to_tempfile(image) : image.to_blob
110
+ image.destroy!
111
+ [
112
+ content,
113
+ {:format => format, :name => "text.#{format}"}
114
+ ]
115
+ end
116
+
117
+ private
118
+
119
+ # Use css-style padding declaration, i.e.
120
+ # 10 (all sides)
121
+ # 10 5 (top/bottom, left/right)
122
+ # 10 5 10 (top, left/right, bottom)
123
+ # 10 5 10 5 (top, right, bottom, left)
124
+ def parse_padding_string(str)
125
+ padding_parts = str.gsub('px','').split(/\s+/).map{|px| px.to_i}
126
+ case padding_parts.size
127
+ when 1
128
+ p = padding_parts.first
129
+ [p,p,p,p]
130
+ when 2
131
+ p,q = padding_parts
132
+ [p,q,p,q]
133
+ when 3
134
+ p,q,r = padding_parts
135
+ [p,q,r,q]
136
+ when 4
137
+ padding_parts
138
+ else raise ArgumentError, "Couldn't parse padding string '#{str}' - should be a css-style string"
139
+ end
140
+ end
141
+
142
+ def scale_factor_for(font_size)
143
+ # Scale approximately to 64 if below
144
+ min_size = 64
145
+ if font_size < min_size
146
+ (min_size.to_f / font_size).ceil
147
+ else
148
+ 1
149
+ end.to_f
150
+ end
151
+
152
+ end
153
+
154
+ end
155
+ end
@@ -0,0 +1,9 @@
1
+ module Dragonfly
2
+ class Generator < FunctionManager
3
+
4
+ def generate(method, *args)
5
+ call_last(method, *args)
6
+ end
7
+
8
+ end
9
+ end
@@ -0,0 +1,81 @@
1
+ require 'tempfile'
2
+
3
+ module Dragonfly
4
+ module ImageMagickUtils
5
+
6
+ # Exceptions
7
+ class ShellCommandFailed < RuntimeError; end
8
+
9
+ class << self
10
+ include Configurable
11
+ configurable_attr :convert_command, "convert"
12
+ configurable_attr :identify_command, "identify"
13
+ configurable_attr :log_commands, false
14
+ end
15
+
16
+ include Loggable
17
+
18
+ private
19
+
20
+ def convert(temp_object, args='', format=nil)
21
+ tempfile = new_tempfile(format)
22
+ run "#{convert_command} #{args} #{temp_object.path} #{tempfile.path}"
23
+ tempfile
24
+ end
25
+
26
+ def identify(temp_object)
27
+ # example of details string:
28
+ # myimage.png PNG 200x100 200x100+0+0 8-bit DirectClass 31.2kb
29
+ details = raw_identify(temp_object)
30
+ filename, format, geometry, geometry_2, depth, image_class, size = details.split(' ')
31
+ width, height = geometry.split('x')
32
+ {
33
+ :filename => filename,
34
+ :format => format.downcase.to_sym,
35
+ :width => width.to_i,
36
+ :height => height.to_i,
37
+ :depth => depth.to_i,
38
+ :image_class => image_class
39
+ }
40
+ end
41
+
42
+ def raw_identify(temp_object, args='')
43
+ run "#{identify_command} #{args} #{temp_object.path}"
44
+ end
45
+
46
+ def new_tempfile(ext=nil)
47
+ tempfile = ext ? Tempfile.new(['dragonfly', ".#{ext}"]) : Tempfile.new('dragonfly')
48
+ tempfile.binmode
49
+ tempfile.close
50
+ tempfile
51
+ end
52
+
53
+ def convert_command
54
+ ImageMagickUtils.convert_command
55
+ end
56
+
57
+ def identify_command
58
+ ImageMagickUtils.identify_command
59
+ end
60
+
61
+ def run(command)
62
+ log.debug("Running command: #{command}") if ImageMagickUtils.log_commands
63
+ begin
64
+ result = `#{command}`
65
+ rescue Errno::ENOENT
66
+ raise_shell_command_failed(command)
67
+ end
68
+ if $?.exitstatus == 1
69
+ throw :unable_to_handle
70
+ elsif !$?.success?
71
+ raise_shell_command_failed(command)
72
+ end
73
+ result
74
+ end
75
+
76
+ def raise_shell_command_failed(command)
77
+ raise ShellCommandFailed, "Command failed (#{command}) with exit status #{$?.exitstatus}"
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,371 @@
1
+ require 'forwardable'
2
+ require 'digest/sha1'
3
+ require 'base64'
4
+
5
+ module Dragonfly
6
+ class Job
7
+
8
+ # Exceptions
9
+ class AppDoesNotMatch < StandardError; end
10
+ class JobAlreadyApplied < StandardError; end
11
+ class NothingToProcess < StandardError; end
12
+ class NothingToEncode < StandardError; end
13
+ class NothingToAnalyse < StandardError; end
14
+ class InvalidArray < StandardError; end
15
+ class NoSHAGiven < StandardError; end
16
+ class IncorrectSHA < StandardError; end
17
+
18
+ extend Forwardable
19
+ def_delegators :result, :data, :file, :tempfile, :path, :to_file, :size, :ext, :name, :name=, :basename, :meta, :meta=, :format, :_format
20
+
21
+ class Step
22
+
23
+ class << self
24
+ # Dragonfly::Job::Fetch -> 'Fetch'
25
+ def basename
26
+ @basename ||= name.split('::').last
27
+ end
28
+ # Dragonfly::Job::Fetch -> :fetch
29
+ def step_name
30
+ @step_name ||= basename.gsub(/[A-Z]/){ "_#{$&.downcase}" }.sub('_','').to_sym
31
+ end
32
+ # Dragonfly::Job::Fetch -> :f
33
+ def abbreviation
34
+ @abbreviation ||= basename.scan(/[A-Z]/).join.downcase.to_sym
35
+ end
36
+ end
37
+
38
+ def initialize(*args)
39
+ @args = args
40
+ end
41
+
42
+ attr_reader :args
43
+
44
+ def update_temp_object(job, content, extra)
45
+ temp_object = TempObject.new(content, job.temp_object.attributes)
46
+ temp_object.extract_attributes_from(extra) if extra
47
+ job.temp_object = temp_object
48
+ end
49
+
50
+ def inspect
51
+ "#{self.class.step_name}(#{args.map{|a| a.inspect }.join(', ')})"
52
+ end
53
+ end
54
+
55
+ class Fetch < Step
56
+ def uid
57
+ args.first
58
+ end
59
+ def apply(job)
60
+ content, extra = job.app.datastore.retrieve(uid)
61
+ job.temp_object = TempObject.new(content, extra)
62
+ end
63
+ end
64
+
65
+ class Process < Step
66
+ def name
67
+ args.first
68
+ end
69
+ def arguments
70
+ args[1..-1]
71
+ end
72
+ def apply(job)
73
+ raise NothingToProcess, "Can't process because temp object has not been initialized. Need to fetch first?" unless job.temp_object
74
+ content, extra = job.app.processor.process(job.temp_object, name, *arguments)
75
+ update_temp_object(job, content, extra)
76
+ end
77
+ end
78
+
79
+ class Encode < Step
80
+ def format
81
+ args.first
82
+ end
83
+ def arguments
84
+ args[1..-1]
85
+ end
86
+ def apply(job)
87
+ raise NothingToEncode, "Can't encode because temp object has not been initialized. Need to fetch first?" unless job.temp_object
88
+ content, extra = job.app.encoder.encode(job.temp_object, format, *arguments)
89
+ update_temp_object(job, content, extra)
90
+ job.temp_object.format = format
91
+ end
92
+ end
93
+
94
+ class Generate < Step
95
+ def apply(job)
96
+ content, extra = job.app.generator.generate(*args)
97
+ job.temp_object = TempObject.new(content, extra)
98
+ end
99
+ end
100
+
101
+ class FetchFile < Step
102
+ def path
103
+ File.expand_path(args.first)
104
+ end
105
+ def apply(job)
106
+ job.temp_object = TempObject.new(File.new(path))
107
+ end
108
+ end
109
+
110
+ STEPS = [
111
+ Fetch,
112
+ Process,
113
+ Encode,
114
+ Generate,
115
+ FetchFile
116
+ ]
117
+
118
+ # Class methods
119
+ class << self
120
+
121
+ def from_a(steps_array, app)
122
+ unless steps_array.is_a?(Array) &&
123
+ steps_array.all?{|s| s.is_a?(Array) && step_abbreviations[s.first] }
124
+ raise InvalidArray, "can't define a job from #{steps_array.inspect}"
125
+ end
126
+ job = app.new_job
127
+ steps_array.each do |step_array|
128
+ step_class = step_abbreviations[step_array.shift]
129
+ job.steps << step_class.new(*step_array)
130
+ end
131
+ job
132
+ end
133
+
134
+ def from_path(path, app)
135
+ path = path.dup
136
+ path.sub!(app.url_path_prefix, '') if app.url_path_prefix
137
+ path.sub!('/', '')
138
+ deserialize(path, app)
139
+ end
140
+
141
+ def deserialize(string, app)
142
+ from_a(Serializer.marshal_decode(string), app)
143
+ end
144
+
145
+ def step_abbreviations
146
+ @step_abbreviations ||= STEPS.inject({}){|hash, step_class| hash[step_class.abbreviation] = step_class; hash }
147
+ end
148
+
149
+ def step_names
150
+ @step_names ||= STEPS.map{|step_class| step_class.step_name }
151
+ end
152
+
153
+ end
154
+
155
+ # Instance methods
156
+
157
+ def initialize(app, temp_object=nil)
158
+ @app = app
159
+ self.extend app.analyser.analysis_methods
160
+ self.extend app.job_definitions
161
+ @steps = []
162
+ @next_step_index = 0
163
+ @temp_object = temp_object
164
+ end
165
+
166
+ # Used by 'dup' and 'clone'
167
+ def initialize_copy(other)
168
+ self.steps = other.steps.dup
169
+ self.extend app.analyser.analysis_methods
170
+ self.extend app.job_definitions
171
+ end
172
+
173
+ attr_accessor :temp_object
174
+ attr_reader :app, :steps
175
+
176
+ # define fetch(), fetch!(), process(), etc.
177
+ STEPS.each do |step_class|
178
+ class_eval %(
179
+ def #{step_class.step_name}(*args)
180
+ new_job = self.dup
181
+ new_job.steps << #{step_class}.new(*args)
182
+ new_job
183
+ end
184
+
185
+ def #{step_class.step_name}!(*args)
186
+ steps << #{step_class}.new(*args)
187
+ self
188
+ end
189
+ )
190
+ end
191
+
192
+ def analyse(method, *args)
193
+ unless result
194
+ raise NothingToAnalyse, "Can't analyse because temp object has not been initialized. Need to fetch first?"
195
+ end
196
+ # Hacky - wish there was a nicer way to do this without extending with yet another module
197
+ if method == :format
198
+ _format || analyser.analyse(result, method, *args)
199
+ else
200
+ analyser.analyse(result, method, *args)
201
+ end
202
+ end
203
+
204
+ def +(other_job)
205
+ unless app == other_job.app
206
+ raise AppDoesNotMatch, "Cannot add jobs belonging to different apps (#{app} is not #{other_job.app})"
207
+ end
208
+ unless other_job.applied_steps.empty?
209
+ raise JobAlreadyApplied, "Cannot add jobs when the second one has already been applied (#{other_job})"
210
+ end
211
+ new_job = self.class.new(app, temp_object)
212
+ new_job.steps = steps + other_job.steps
213
+ new_job.next_step_index = next_step_index
214
+ new_job
215
+ end
216
+
217
+ # Applying, etc.
218
+
219
+ def apply
220
+ pending_steps.each{|step| step.apply(self) }
221
+ self.next_step_index = steps.length
222
+ self
223
+ end
224
+
225
+ def result
226
+ apply
227
+ temp_object
228
+ end
229
+
230
+ def applied_steps
231
+ steps[0...next_step_index]
232
+ end
233
+
234
+ def pending_steps
235
+ steps[next_step_index..-1]
236
+ end
237
+
238
+ def to_a
239
+ steps.map{|step|
240
+ [step.class.abbreviation, *step.args]
241
+ }
242
+ end
243
+
244
+ # Serializing, etc.
245
+
246
+ def serialize
247
+ Serializer.marshal_encode(to_a)
248
+ end
249
+
250
+ def unique_signature
251
+ Digest::SHA1.hexdigest(serialize)
252
+ end
253
+
254
+ def sha
255
+ Digest::SHA1.hexdigest("#{serialize}#{app.secret}")[0...8]
256
+ end
257
+
258
+ def validate_sha!(sha)
259
+ case sha
260
+ when nil
261
+ raise NoSHAGiven
262
+ when self.sha
263
+ self
264
+ else
265
+ raise IncorrectSHA, sha
266
+ end
267
+ end
268
+
269
+ # URLs, etc.
270
+
271
+ def url(*args)
272
+ app.url_for(self, *args) unless steps.empty?
273
+ end
274
+
275
+ def b64_data
276
+ "data:#{resolve_mime_type};base64,#{Base64.encode64(data)}"
277
+ end
278
+
279
+ # to_stuff...
280
+
281
+ def to_app
282
+ JobEndpoint.new(self)
283
+ end
284
+
285
+ def to_response(env={})
286
+ to_app.call(env)
287
+ end
288
+
289
+ def to_path
290
+ "/#{serialize}"
291
+ end
292
+
293
+ def to_fetched_job(uid)
294
+ new_job = self.class.new(app, temp_object)
295
+ new_job.fetch!(uid)
296
+ new_job.next_step_index = 1
297
+ new_job
298
+ end
299
+
300
+ # Step inspection
301
+
302
+ def fetch_step
303
+ last_step_of_type(Fetch)
304
+ end
305
+
306
+ def uid
307
+ step = fetch_step
308
+ step.uid if step
309
+ end
310
+
311
+ def uid_basename
312
+ File.basename(uid, '.*') if uid
313
+ end
314
+
315
+ def uid_extname
316
+ File.extname(uid) if uid
317
+ end
318
+
319
+ def generate_step
320
+ last_step_of_type(Generate)
321
+ end
322
+
323
+ def fetch_file_step
324
+ last_step_of_type(FetchFile)
325
+ end
326
+
327
+ def process_steps
328
+ steps.select{|s| s.is_a?(Process) }
329
+ end
330
+
331
+ def encode_step
332
+ last_step_of_type(Encode)
333
+ end
334
+
335
+ def encoded_format
336
+ step = encode_step
337
+ step.format if step
338
+ end
339
+
340
+ def encoded_extname
341
+ format = encoded_format
342
+ ".#{format}" if format
343
+ end
344
+
345
+ # Misc
346
+
347
+ def store(opts={})
348
+ app.store(result, opts)
349
+ end
350
+
351
+ def resolve_mime_type
352
+ app.resolve_mime_type(result)
353
+ end
354
+
355
+ def inspect
356
+ to_s.sub(/>$/, " app=#{app}, steps=#{steps.inspect}, temp_object=#{temp_object.inspect}, steps applied:#{applied_steps.length}/#{steps.length} >")
357
+ end
358
+
359
+ protected
360
+
361
+ attr_writer :steps
362
+ attr_accessor :next_step_index
363
+
364
+ private
365
+
366
+ def last_step_of_type(type)
367
+ steps.select{|s| s.is_a?(type) }.last
368
+ end
369
+
370
+ end
371
+ end