dragonfly 0.6.2 → 0.7.0
Sign up to get free protection for your applications and to get access to all the features.
Potentially problematic release.
This version of dragonfly might be problematic. Click here for more details.
- data/.gitignore +2 -0
- data/.specopts +2 -0
- data/.yardopts +11 -5
- data/Gemfile +22 -0
- data/Gemfile.rails.2.3.5 +13 -0
- data/History.md +49 -0
- data/README.md +18 -28
- data/Rakefile +24 -36
- data/VERSION +1 -1
- data/config.ru +4 -1
- data/dragonfly.gemspec +85 -99
- data/extra_docs/Analysers.md +66 -30
- data/extra_docs/Caching.md +22 -0
- data/extra_docs/Configuration.md +116 -0
- data/extra_docs/DataStorage.md +114 -14
- data/extra_docs/Encoding.md +62 -37
- data/extra_docs/GeneralUsage.md +118 -0
- data/extra_docs/Generators.md +92 -0
- data/extra_docs/Heroku.md +51 -0
- data/extra_docs/Index.md +8 -9
- data/extra_docs/MimeTypes.md +18 -17
- data/extra_docs/Models.md +251 -0
- data/extra_docs/Processing.md +94 -70
- data/extra_docs/Rack.md +53 -0
- data/extra_docs/Rails2.md +44 -0
- data/extra_docs/Rails3.md +51 -0
- data/extra_docs/Sinatra.md +21 -0
- data/extra_docs/URLs.md +114 -0
- data/features/images.feature +6 -7
- data/features/no_processing.feature +0 -6
- data/features/rails_2.3.5.feature +1 -1
- data/features/rails_3.0.0.rc.feature +8 -0
- data/features/steps/dragonfly_steps.rb +14 -12
- data/features/steps/rails_steps.rb +20 -9
- data/features/support/env.rb +10 -11
- data/fixtures/files/app/views/albums/new.html.erb +4 -4
- data/fixtures/files/app/views/albums/show.html.erb +1 -1
- data/fixtures/files/features/manage_album_images.feature +1 -1
- data/fixtures/files/features/step_definitions/{album_steps.rb → image_steps.rb} +4 -3
- data/fixtures/files/features/support/paths.rb +2 -0
- data/fixtures/files/features/text_images.feature +7 -0
- data/fixtures/rails_3.0.0.rc/template.rb +21 -0
- data/irbrc.rb +2 -1
- data/lib/dragonfly.rb +4 -16
- data/lib/dragonfly/{active_record_extensions.rb → active_model_extensions.rb} +1 -1
- data/lib/dragonfly/active_model_extensions/attachment.rb +146 -0
- data/lib/dragonfly/{active_record_extensions → active_model_extensions}/class_methods.rb +5 -6
- data/lib/dragonfly/{active_record_extensions → active_model_extensions}/instance_methods.rb +1 -1
- data/lib/dragonfly/{active_record_extensions → active_model_extensions}/validations.rb +5 -9
- data/lib/dragonfly/analyser.rb +59 -0
- data/lib/dragonfly/analysis/file_command_analyser.rb +1 -1
- data/lib/dragonfly/analysis/r_magick_analyser.rb +46 -31
- data/lib/dragonfly/app.rb +138 -173
- data/lib/dragonfly/config/heroku.rb +19 -0
- data/lib/dragonfly/config/r_magick.rb +37 -0
- data/lib/dragonfly/config/{rails_defaults.rb → rails.rb} +6 -7
- data/lib/dragonfly/configurable.rb +30 -27
- data/lib/dragonfly/core_ext/object.rb +1 -1
- data/lib/dragonfly/data_storage/file_data_store.rb +59 -26
- data/lib/dragonfly/data_storage/mongo_data_store.rb +65 -0
- data/lib/dragonfly/data_storage/s3data_store.rb +31 -12
- data/lib/dragonfly/encoder.rb +13 -0
- data/lib/dragonfly/encoding/r_magick_encoder.rb +10 -19
- data/lib/dragonfly/endpoint.rb +43 -0
- data/lib/dragonfly/function_manager.rb +65 -0
- data/lib/dragonfly/{processing/r_magick_text_processor.rb → generation/r_magick_generator.rb} +25 -11
- data/lib/dragonfly/generator.rb +9 -0
- data/lib/dragonfly/job.rb +290 -0
- data/lib/dragonfly/job_builder.rb +39 -0
- data/lib/dragonfly/job_definitions.rb +26 -0
- data/lib/dragonfly/job_endpoint.rb +17 -0
- data/lib/dragonfly/loggable.rb +28 -0
- data/lib/dragonfly/middleware.rb +21 -14
- data/lib/dragonfly/processing/r_magick_processor.rb +71 -48
- data/lib/dragonfly/processor.rb +9 -0
- data/lib/dragonfly/r_magick_utils.rb +24 -0
- data/lib/dragonfly/rails/images.rb +10 -7
- data/lib/dragonfly/routed_endpoint.rb +42 -0
- data/lib/dragonfly/serializer.rb +32 -0
- data/lib/dragonfly/simple_cache.rb +23 -0
- data/lib/dragonfly/simple_endpoint.rb +64 -0
- data/lib/dragonfly/temp_object.rb +77 -45
- data/spec/argument_matchers.rb +7 -17
- data/spec/dragonfly/active_model_extensions/active_model_setup.rb +97 -0
- data/spec/dragonfly/active_model_extensions/active_record_setup.rb +85 -0
- data/spec/dragonfly/{active_record_extensions → active_model_extensions}/model_spec.rb +282 -244
- data/spec/dragonfly/active_model_extensions/spec_helper.rb +11 -0
- data/spec/dragonfly/analyser_spec.rb +123 -0
- data/spec/dragonfly/analysis/file_command_analyser_spec.rb +2 -2
- data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +10 -1
- data/spec/dragonfly/app_spec.rb +175 -69
- data/spec/dragonfly/configurable_spec.rb +14 -0
- data/spec/dragonfly/data_storage/data_store_spec.rb +36 -9
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +61 -38
- data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +18 -0
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +34 -39
- data/spec/dragonfly/deprecation_spec.rb +20 -0
- data/spec/dragonfly/function_manager_spec.rb +154 -0
- data/spec/dragonfly/generation/r_magick_generator_spec.rb +119 -0
- data/spec/dragonfly/job_builder_spec.rb +37 -0
- data/spec/dragonfly/job_definitions_spec.rb +35 -0
- data/spec/dragonfly/job_endpoint_spec.rb +66 -0
- data/spec/dragonfly/job_spec.rb +605 -0
- data/spec/dragonfly/loggable_spec.rb +80 -0
- data/spec/dragonfly/middleware_spec.rb +37 -17
- data/spec/dragonfly/processing/r_magick_processor_spec.rb +182 -166
- data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
- data/spec/dragonfly/serializer_spec.rb +61 -0
- data/spec/dragonfly/simple_cache_spec.rb +27 -0
- data/spec/dragonfly/simple_endpoint_spec.rb +78 -0
- data/spec/dragonfly/temp_object_spec.rb +154 -119
- data/spec/simple_matchers.rb +22 -0
- data/spec/spec_helper.rb +28 -4
- data/yard/templates/default/layout/html/layout.erb +18 -11
- metadata +89 -190
- data/config.rb +0 -5
- data/extra_docs/ActiveRecord.md +0 -196
- data/extra_docs/ExampleUseCases.md +0 -189
- data/extra_docs/GettingStarted.md +0 -114
- data/extra_docs/Shortcuts.md +0 -118
- data/extra_docs/UsingWithRails.md +0 -81
- data/features/rails_3.0.0.beta3.feature +0 -7
- data/fixtures/rails_3.0.0.beta3/template.rb +0 -16
- data/lib/dragonfly/active_record_extensions/attachment.rb +0 -170
- data/lib/dragonfly/analyser_list.rb +0 -9
- data/lib/dragonfly/analysis/base.rb +0 -10
- data/lib/dragonfly/belongs_to_app.rb +0 -24
- data/lib/dragonfly/config/heroku_rails_images.rb +0 -23
- data/lib/dragonfly/config/r_magick_images.rb +0 -69
- data/lib/dragonfly/config/r_magick_text.rb +0 -25
- data/lib/dragonfly/config/rails_images.rb +0 -13
- data/lib/dragonfly/data_storage/base.rb +0 -21
- data/lib/dragonfly/data_storage/base64_data_store.rb +0 -23
- data/lib/dragonfly/data_storage/transparent_data_store.rb +0 -21
- data/lib/dragonfly/delegatable.rb +0 -14
- data/lib/dragonfly/delegator.rb +0 -62
- data/lib/dragonfly/encoder_list.rb +0 -9
- data/lib/dragonfly/encoding/base.rb +0 -14
- data/lib/dragonfly/encoding/transparent_encoder.rb +0 -14
- data/lib/dragonfly/extended_temp_object.rb +0 -120
- data/lib/dragonfly/parameters.rb +0 -163
- data/lib/dragonfly/processing/base.rb +0 -10
- data/lib/dragonfly/processor_list.rb +0 -9
- data/lib/dragonfly/url_handler.rb +0 -147
- data/spec/dragonfly/active_record_extensions/attachment_spec.rb +0 -8
- data/spec/dragonfly/active_record_extensions/migration.rb +0 -42
- data/spec/dragonfly/active_record_extensions/models.rb +0 -6
- data/spec/dragonfly/active_record_extensions/spec_helper.rb +0 -24
- data/spec/dragonfly/belongs_to_app_spec.rb +0 -55
- data/spec/dragonfly/delegatable_spec.rb +0 -32
- data/spec/dragonfly/delegator_spec.rb +0 -145
- data/spec/dragonfly/extended_temp_object_spec.rb +0 -71
- data/spec/dragonfly/parameters_spec.rb +0 -298
- data/spec/dragonfly/processing/r_magick_text_processor_spec.rb +0 -84
- data/spec/dragonfly/url_handler_spec.rb +0 -247
- data/spec/dragonfly_spec.rb +0 -16
- data/spec/ginger_scenarios.rb +0 -13
@@ -2,10 +2,10 @@ require 'RMagick'
|
|
2
2
|
|
3
3
|
module Dragonfly
|
4
4
|
module Encoding
|
5
|
-
|
6
|
-
class RMagickEncoder < Base
|
5
|
+
class RMagickEncoder
|
7
6
|
|
8
7
|
include Configurable
|
8
|
+
include RMagickUtils
|
9
9
|
|
10
10
|
configurable_attr :supported_formats, [
|
11
11
|
:ai,
|
@@ -42,28 +42,19 @@ module Dragonfly
|
|
42
42
|
:xwd
|
43
43
|
]
|
44
44
|
|
45
|
-
def encode(
|
45
|
+
def encode(temp_object, format, encoding={})
|
46
46
|
format = format.to_s.downcase
|
47
47
|
throw :unable_to_handle unless supported_formats.include?(format.to_sym)
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
48
|
+
rmagick_image(temp_object) do |image|
|
49
|
+
if image.format.downcase == format
|
50
|
+
temp_object # do nothing
|
51
|
+
else
|
52
|
+
image.format = format
|
53
|
+
image
|
54
|
+
end
|
54
55
|
end
|
55
56
|
end
|
56
57
|
|
57
|
-
private
|
58
|
-
|
59
|
-
def rmagick_image(temp_object)
|
60
|
-
Magick::Image.from_blob(temp_object.data).first
|
61
|
-
rescue Magick::ImageMagickError => e
|
62
|
-
log.warn("Unable to handle content in #{self.class} - got:\n#{e}")
|
63
|
-
throw :unable_to_handle
|
64
|
-
end
|
65
|
-
|
66
58
|
end
|
67
|
-
|
68
59
|
end
|
69
60
|
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module Endpoint
|
3
|
+
|
4
|
+
class EmptyJob < StandardError; end
|
5
|
+
|
6
|
+
private
|
7
|
+
|
8
|
+
def response_for_job(job, env)
|
9
|
+
if etag_matches?(job, env)
|
10
|
+
[304, cache_headers(job), []]
|
11
|
+
else
|
12
|
+
[200, success_headers(job), job.result] # Successful response
|
13
|
+
end
|
14
|
+
rescue DataStorage::DataNotFound => e
|
15
|
+
[404, {"Content-Type" => 'text/plain'}, [e.message]]
|
16
|
+
end
|
17
|
+
|
18
|
+
def cache_headers(job)
|
19
|
+
{
|
20
|
+
"Cache-Control" => "public, max-age=#{job.app.cache_duration}",
|
21
|
+
"ETag" => %("#{job.unique_signature}")
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
def etag_matches?(job, env)
|
26
|
+
if_none_match = env['HTTP_IF_NONE_MATCH']
|
27
|
+
if if_none_match
|
28
|
+
if_none_match.tr!('"','')
|
29
|
+
if_none_match.split(',').include?(job.unique_signature) || if_none_match == '*'
|
30
|
+
else
|
31
|
+
false
|
32
|
+
end
|
33
|
+
end
|
34
|
+
|
35
|
+
def success_headers(job)
|
36
|
+
{
|
37
|
+
"Content-Type" => job.app.resolve_mime_type(job.result),
|
38
|
+
"Content-Length" => job.size.to_s,
|
39
|
+
}.merge(cache_headers(job))
|
40
|
+
end
|
41
|
+
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
class FunctionManager
|
3
|
+
|
4
|
+
# Exceptions
|
5
|
+
class NotDefined < NoMethodError; end
|
6
|
+
class UnableToHandle < NotImplementedError; end
|
7
|
+
|
8
|
+
include Loggable
|
9
|
+
|
10
|
+
def initialize
|
11
|
+
@functions = {}
|
12
|
+
@objects = []
|
13
|
+
end
|
14
|
+
|
15
|
+
def add(name, callable_obj=nil, &block)
|
16
|
+
functions[name] ||= []
|
17
|
+
functions[name] << (callable_obj || block)
|
18
|
+
end
|
19
|
+
|
20
|
+
attr_reader :functions, :objects
|
21
|
+
|
22
|
+
def register(klass, *args, &block)
|
23
|
+
obj = klass.new(*args)
|
24
|
+
obj.configure(&block) if block
|
25
|
+
obj.use_same_log_as(self) if obj.is_a?(Loggable)
|
26
|
+
methods_to_add(obj).each do |meth|
|
27
|
+
add meth.to_sym, obj.method(meth)
|
28
|
+
end
|
29
|
+
objects << obj
|
30
|
+
obj
|
31
|
+
end
|
32
|
+
|
33
|
+
def call_last(meth, *args)
|
34
|
+
if functions[meth.to_sym]
|
35
|
+
functions[meth.to_sym].reverse.each do |function|
|
36
|
+
catch :unable_to_handle do
|
37
|
+
return function.call(*args)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
# If the code gets here, then none of the registered functions were able to handle the method call
|
41
|
+
raise UnableToHandle, "None of the functions registered with #{self} were able to deal with the method call " +
|
42
|
+
"#{meth}(#{args.map{|a| a.inspect[0..100]}.join(',')}). You may need to register one that can."
|
43
|
+
else
|
44
|
+
raise NotDefined, "function #{meth} not registered with #{self}"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def inspect
|
49
|
+
to_s.sub(/>$/, " with functions: #{functions.keys.map{|k| k.to_s }.sort.join(', ')} >")
|
50
|
+
end
|
51
|
+
|
52
|
+
private
|
53
|
+
|
54
|
+
def methods_to_add(obj)
|
55
|
+
if obj.is_a?(Configurable)
|
56
|
+
obj.public_methods(false) -
|
57
|
+
obj.configuration_methods.map{|meth| meth.to_method_name} -
|
58
|
+
[:configuration_methods.to_method_name]
|
59
|
+
else
|
60
|
+
obj.public_methods(false)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
end
|
65
|
+
end
|
data/lib/dragonfly/{processing/r_magick_text_processor.rb → generation/r_magick_generator.rb}
RENAMED
@@ -1,9 +1,8 @@
|
|
1
1
|
require 'RMagick'
|
2
2
|
|
3
3
|
module Dragonfly
|
4
|
-
module
|
5
|
-
|
6
|
-
class RMagickTextProcessor < Base
|
4
|
+
module Generation
|
5
|
+
class RMagickGenerator
|
7
6
|
|
8
7
|
FONT_STYLES = {
|
9
8
|
'normal' => Magick::NormalStyle,
|
@@ -56,8 +55,19 @@ module Dragonfly
|
|
56
55
|
)
|
57
56
|
end
|
58
57
|
end
|
59
|
-
|
60
|
-
def
|
58
|
+
|
59
|
+
def plasma(width, height, format='png')
|
60
|
+
image = Magick::Image.read("plasma:fractal"){self.size = "#{width}x#{height}"}.first
|
61
|
+
image.format = format.to_s
|
62
|
+
data = image.to_blob
|
63
|
+
image.destroy!
|
64
|
+
[
|
65
|
+
data,
|
66
|
+
{:format => format.to_sym, :name => "plasma.#{format}"}
|
67
|
+
]
|
68
|
+
end
|
69
|
+
|
70
|
+
def text(text_string, opts={})
|
61
71
|
opts = HashWithCssStyleKeys[opts]
|
62
72
|
|
63
73
|
draw = Magick::Draw.new
|
@@ -89,10 +99,8 @@ module Dragonfly
|
|
89
99
|
padding_bottom = (opts[:padding_bottom] || pb || 0) * s
|
90
100
|
padding_left = (opts[:padding_left] || pl || 0) * s
|
91
101
|
|
92
|
-
text = temp_object.data
|
93
|
-
|
94
102
|
# Calculate (scaled up) dimensions
|
95
|
-
metrics = draw.get_type_metrics(
|
103
|
+
metrics = draw.get_type_metrics(text_string)
|
96
104
|
width, height = metrics.width, metrics.height
|
97
105
|
|
98
106
|
scaled_up_width = padding_left + width + padding_right
|
@@ -103,15 +111,21 @@ module Dragonfly
|
|
103
111
|
self.background_color = opts[:background_color] || 'transparent'
|
104
112
|
}
|
105
113
|
# Draw the text
|
106
|
-
draw.annotate(image, width, height, padding_left, padding_top,
|
114
|
+
draw.annotate(image, width, height, padding_left, padding_top, text_string)
|
107
115
|
|
108
116
|
# Scale back down again
|
109
117
|
image.scale!(1/s)
|
110
118
|
|
111
|
-
|
119
|
+
format = opts[:format] || :png
|
120
|
+
image.format = format.to_s
|
112
121
|
|
113
122
|
# Output image as string
|
114
|
-
image.to_blob
|
123
|
+
data = image.to_blob
|
124
|
+
image.destroy!
|
125
|
+
[
|
126
|
+
data,
|
127
|
+
{:format => format, :name => "text.#{format}"}
|
128
|
+
]
|
115
129
|
end
|
116
130
|
|
117
131
|
private
|
@@ -0,0 +1,290 @@
|
|
1
|
+
require 'forwardable'
|
2
|
+
require 'digest/sha1'
|
3
|
+
|
4
|
+
module Dragonfly
|
5
|
+
class Job
|
6
|
+
|
7
|
+
# Exceptions
|
8
|
+
class AppDoesNotMatch < StandardError; end
|
9
|
+
class JobAlreadyApplied < StandardError; end
|
10
|
+
class NothingToProcess < StandardError; end
|
11
|
+
class NothingToEncode < StandardError; end
|
12
|
+
class NothingToAnalyse < StandardError; end
|
13
|
+
class InvalidArray < StandardError; end
|
14
|
+
class NoSHAGiven < StandardError; end
|
15
|
+
class IncorrectSHA < StandardError; end
|
16
|
+
|
17
|
+
extend Forwardable
|
18
|
+
def_delegators :result, :data, :file, :tempfile, :path, :to_file, :size, :ext, :name, :meta, :format, :_format
|
19
|
+
|
20
|
+
class Step
|
21
|
+
|
22
|
+
class << self
|
23
|
+
# Dragonfly::Job::Fetch -> 'Fetch'
|
24
|
+
def basename
|
25
|
+
@basename ||= name.split('::').last
|
26
|
+
end
|
27
|
+
# Dragonfly::Job::Fetch -> :fetch
|
28
|
+
def step_name
|
29
|
+
@step_name ||= basename.gsub(/[A-Z]/){ "_#{$&.downcase}" }.sub('_','').to_sym
|
30
|
+
end
|
31
|
+
# Dragonfly::Job::Fetch -> :f
|
32
|
+
def abbreviation
|
33
|
+
@abbreviation ||= basename.scan(/[A-Z]/).join.downcase.to_sym
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def initialize(*args)
|
38
|
+
@args = args
|
39
|
+
end
|
40
|
+
attr_reader :args
|
41
|
+
def inspect
|
42
|
+
"#{self.class.step_name}(#{args.map{|a| a.inspect }.join(', ')})"
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
class Fetch < Step
|
47
|
+
def uid
|
48
|
+
args.first
|
49
|
+
end
|
50
|
+
def apply(job)
|
51
|
+
content, extra = job.app.datastore.retrieve(uid)
|
52
|
+
job.temp_object = TempObject.new(content, (extra || {}))
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
class Process < Step
|
57
|
+
def name
|
58
|
+
args.first
|
59
|
+
end
|
60
|
+
def arguments
|
61
|
+
args[1..-1]
|
62
|
+
end
|
63
|
+
def apply(job)
|
64
|
+
raise NothingToProcess, "Can't process because temp object has not been initialized. Need to fetch first?" unless job.temp_object
|
65
|
+
old = job.temp_object
|
66
|
+
job.temp_object = TempObject.new(
|
67
|
+
job.app.processor.process(old, name, *arguments),
|
68
|
+
old.attributes
|
69
|
+
)
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
class Encode < Step
|
74
|
+
def format
|
75
|
+
args.first
|
76
|
+
end
|
77
|
+
def arguments
|
78
|
+
args[1..-1]
|
79
|
+
end
|
80
|
+
def apply(job)
|
81
|
+
raise NothingToEncode, "Can't encode because temp object has not been initialized. Need to fetch first?" unless job.temp_object
|
82
|
+
old = job.temp_object
|
83
|
+
job.temp_object = TempObject.new(
|
84
|
+
job.app.encoder.encode(old, format, *arguments),
|
85
|
+
old.attributes.merge(:format => format)
|
86
|
+
)
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
class Generate < Step
|
91
|
+
def apply(job)
|
92
|
+
content, extra = job.app.generator.generate(*args)
|
93
|
+
job.temp_object = TempObject.new(content, (extra || {}))
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
class FetchFile < Step
|
98
|
+
def path
|
99
|
+
File.expand_path(args.first)
|
100
|
+
end
|
101
|
+
def apply(job)
|
102
|
+
job.temp_object = TempObject.new(File.new(path))
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
STEPS = [
|
107
|
+
Fetch,
|
108
|
+
Process,
|
109
|
+
Encode,
|
110
|
+
Generate,
|
111
|
+
FetchFile
|
112
|
+
]
|
113
|
+
|
114
|
+
# Class methods
|
115
|
+
class << self
|
116
|
+
|
117
|
+
def from_a(steps_array, app)
|
118
|
+
unless steps_array.is_a?(Array) &&
|
119
|
+
steps_array.all?{|s| s.is_a?(Array) && step_abbreviations[s.first] }
|
120
|
+
raise InvalidArray, "can't define a job from #{steps_array.inspect}"
|
121
|
+
end
|
122
|
+
job = app.new_job
|
123
|
+
steps_array.each do |step_array|
|
124
|
+
step_class = step_abbreviations[step_array.shift]
|
125
|
+
job.steps << step_class.new(*step_array)
|
126
|
+
end
|
127
|
+
job
|
128
|
+
end
|
129
|
+
|
130
|
+
def from_path(path, app)
|
131
|
+
path.sub!(app.url_path_prefix, '') if app.url_path_prefix
|
132
|
+
path.sub!('/', '')
|
133
|
+
deserialize(path, app)
|
134
|
+
end
|
135
|
+
|
136
|
+
def deserialize(string, app)
|
137
|
+
from_a(Serializer.marshal_decode(string), app)
|
138
|
+
end
|
139
|
+
|
140
|
+
def step_abbreviations
|
141
|
+
@step_abbreviations ||= STEPS.inject({}){|hash, step_class| hash[step_class.abbreviation] = step_class; hash }
|
142
|
+
end
|
143
|
+
|
144
|
+
def step_names
|
145
|
+
@step_names ||= STEPS.map{|step_class| step_class.step_name }
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
# Instance methods
|
151
|
+
|
152
|
+
def initialize(app, temp_object=nil)
|
153
|
+
@app = app
|
154
|
+
self.extend app.analyser.analysis_methods
|
155
|
+
self.extend app.job_definitions
|
156
|
+
@steps = []
|
157
|
+
@next_step_index = 0
|
158
|
+
@temp_object = temp_object
|
159
|
+
end
|
160
|
+
|
161
|
+
# Used by 'dup' and 'clone'
|
162
|
+
def initialize_copy(other)
|
163
|
+
self.steps = other.steps.dup
|
164
|
+
self.extend app.analyser.analysis_methods
|
165
|
+
self.extend app.job_definitions
|
166
|
+
end
|
167
|
+
|
168
|
+
attr_accessor :temp_object
|
169
|
+
attr_reader :app, :steps
|
170
|
+
|
171
|
+
# define fetch(), fetch!(), process(), etc.
|
172
|
+
STEPS.each do |step_class|
|
173
|
+
class_eval %(
|
174
|
+
def #{step_class.step_name}(*args)
|
175
|
+
new_job = self.dup
|
176
|
+
new_job.steps << #{step_class}.new(*args)
|
177
|
+
new_job
|
178
|
+
end
|
179
|
+
|
180
|
+
def #{step_class.step_name}!(*args)
|
181
|
+
steps << #{step_class}.new(*args)
|
182
|
+
self
|
183
|
+
end
|
184
|
+
)
|
185
|
+
end
|
186
|
+
|
187
|
+
def analyse(method, *args)
|
188
|
+
unless result
|
189
|
+
raise NothingToAnalyse, "Can't analyse because temp object has not been initialized. Need to fetch first?"
|
190
|
+
end
|
191
|
+
# Hacky - wish there was a nicer way to do this without extending with yet another module
|
192
|
+
if method == :format
|
193
|
+
_format || analyser.analyse(result, method, *args)
|
194
|
+
else
|
195
|
+
analyser.analyse(result, method, *args)
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
def +(other_job)
|
200
|
+
unless app == other_job.app
|
201
|
+
raise AppDoesNotMatch, "Cannot add jobs belonging to different apps (#{app} is not #{other_job.app})"
|
202
|
+
end
|
203
|
+
unless other_job.applied_steps.empty?
|
204
|
+
raise JobAlreadyApplied, "Cannot add jobs when the second one has already been applied (#{other_job})"
|
205
|
+
end
|
206
|
+
new_job = self.class.new(app, temp_object)
|
207
|
+
new_job.steps = steps + other_job.steps
|
208
|
+
new_job.next_step_index = next_step_index
|
209
|
+
new_job
|
210
|
+
end
|
211
|
+
|
212
|
+
def apply
|
213
|
+
pending_steps.each{|step| step.apply(self) }
|
214
|
+
self.next_step_index = steps.length
|
215
|
+
self
|
216
|
+
end
|
217
|
+
|
218
|
+
def result
|
219
|
+
apply
|
220
|
+
temp_object
|
221
|
+
end
|
222
|
+
|
223
|
+
def applied_steps
|
224
|
+
steps[0...next_step_index]
|
225
|
+
end
|
226
|
+
|
227
|
+
def pending_steps
|
228
|
+
steps[next_step_index..-1]
|
229
|
+
end
|
230
|
+
|
231
|
+
def to_a
|
232
|
+
steps.map{|step|
|
233
|
+
[step.class.abbreviation, *step.args]
|
234
|
+
}
|
235
|
+
end
|
236
|
+
|
237
|
+
def serialize
|
238
|
+
Serializer.marshal_encode(to_a)
|
239
|
+
end
|
240
|
+
alias unique_signature serialize
|
241
|
+
|
242
|
+
def sha
|
243
|
+
Digest::SHA1.hexdigest("#{serialize}#{app.secret}")[0...8]
|
244
|
+
end
|
245
|
+
|
246
|
+
def validate_sha!(sha)
|
247
|
+
case sha
|
248
|
+
when nil
|
249
|
+
raise NoSHAGiven
|
250
|
+
when self.sha
|
251
|
+
self
|
252
|
+
else
|
253
|
+
raise IncorrectSHA, sha
|
254
|
+
end
|
255
|
+
end
|
256
|
+
|
257
|
+
def to_app
|
258
|
+
JobEndpoint.new(self)
|
259
|
+
end
|
260
|
+
|
261
|
+
def to_response
|
262
|
+
to_app.call
|
263
|
+
end
|
264
|
+
|
265
|
+
def url(*args)
|
266
|
+
app.url_for(self, *args) unless steps.empty?
|
267
|
+
end
|
268
|
+
|
269
|
+
def to_fetched_job(uid)
|
270
|
+
new_job = self.class.new(app, temp_object)
|
271
|
+
new_job.fetch!(uid)
|
272
|
+
new_job.next_step_index = 1
|
273
|
+
new_job
|
274
|
+
end
|
275
|
+
|
276
|
+
def to_path
|
277
|
+
"/#{serialize}"
|
278
|
+
end
|
279
|
+
|
280
|
+
def inspect
|
281
|
+
to_s.sub(/>$/, " app=#{app}, steps=#{steps.inspect}, temp_object=#{temp_object.inspect}, steps applied:#{applied_steps.length}/#{steps.length} >")
|
282
|
+
end
|
283
|
+
|
284
|
+
protected
|
285
|
+
|
286
|
+
attr_writer :steps
|
287
|
+
attr_accessor :next_step_index
|
288
|
+
|
289
|
+
end
|
290
|
+
end
|