fog-dragonfly 0.8.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.
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