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.
- data/.specopts +2 -0
- data/.yardopts +23 -0
- data/Gemfile +23 -0
- data/Gemfile.rails.2.3.5 +14 -0
- data/History.md +266 -0
- data/LICENSE +20 -0
- data/README.md +88 -0
- data/Rakefile +92 -0
- data/VERSION +1 -0
- data/config.ru +13 -0
- data/docs.watchr +1 -0
- data/dragonfly.gemspec +293 -0
- data/extra_docs/Analysers.md +108 -0
- data/extra_docs/Caching.md +23 -0
- data/extra_docs/Configuration.md +138 -0
- data/extra_docs/DataStorage.md +136 -0
- data/extra_docs/Encoding.md +96 -0
- data/extra_docs/GeneralUsage.md +121 -0
- data/extra_docs/Generators.md +102 -0
- data/extra_docs/Heroku.md +50 -0
- data/extra_docs/Index.md +36 -0
- data/extra_docs/MimeTypes.md +40 -0
- data/extra_docs/Models.md +266 -0
- data/extra_docs/Mongo.md +45 -0
- data/extra_docs/Processing.md +130 -0
- data/extra_docs/Rack.md +52 -0
- data/extra_docs/Rails2.md +55 -0
- data/extra_docs/Rails3.md +62 -0
- data/extra_docs/Sinatra.md +25 -0
- data/extra_docs/URLs.md +169 -0
- data/features/3.0.3.feature +8 -0
- data/features/images.feature +47 -0
- data/features/no_processing.feature +14 -0
- data/features/rails_2.3.5.feature +7 -0
- data/features/steps/common_steps.rb +8 -0
- data/features/steps/dragonfly_steps.rb +66 -0
- data/features/steps/rails_steps.rb +39 -0
- data/features/support/env.rb +40 -0
- data/fixtures/files/app/models/album.rb +3 -0
- data/fixtures/files/app/views/albums/new.html.erb +4 -0
- data/fixtures/files/app/views/albums/show.html.erb +4 -0
- data/fixtures/files/config/initializers/dragonfly.rb +4 -0
- data/fixtures/files/features/manage_album_images.feature +12 -0
- data/fixtures/files/features/step_definitions/image_steps.rb +15 -0
- data/fixtures/files/features/support/paths.rb +15 -0
- data/fixtures/files/features/text_images.feature +7 -0
- data/fixtures/rails_2.3.5/template.rb +10 -0
- data/fixtures/rails_3.0.3/template.rb +20 -0
- data/irbrc.rb +17 -0
- data/lib/dragonfly.rb +45 -0
- data/lib/dragonfly/active_model_extensions.rb +13 -0
- data/lib/dragonfly/active_model_extensions/attachment.rb +169 -0
- data/lib/dragonfly/active_model_extensions/class_methods.rb +45 -0
- data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
- data/lib/dragonfly/active_model_extensions/validations.rb +37 -0
- data/lib/dragonfly/analyser.rb +59 -0
- data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
- data/lib/dragonfly/analysis/image_magick_analyser.rb +47 -0
- data/lib/dragonfly/analysis/r_magick_analyser.rb +63 -0
- data/lib/dragonfly/app.rb +182 -0
- data/lib/dragonfly/config/heroku.rb +19 -0
- data/lib/dragonfly/config/image_magick.rb +41 -0
- data/lib/dragonfly/config/r_magick.rb +46 -0
- data/lib/dragonfly/config/rails.rb +17 -0
- data/lib/dragonfly/configurable.rb +119 -0
- data/lib/dragonfly/core_ext/object.rb +8 -0
- data/lib/dragonfly/core_ext/string.rb +9 -0
- data/lib/dragonfly/core_ext/symbol.rb +9 -0
- data/lib/dragonfly/data_storage.rb +9 -0
- data/lib/dragonfly/data_storage/file_data_store.rb +114 -0
- data/lib/dragonfly/data_storage/mongo_data_store.rb +82 -0
- data/lib/dragonfly/data_storage/s3data_store.rb +115 -0
- data/lib/dragonfly/encoder.rb +13 -0
- data/lib/dragonfly/encoding/image_magick_encoder.rb +57 -0
- data/lib/dragonfly/encoding/r_magick_encoder.rb +61 -0
- data/lib/dragonfly/function_manager.rb +69 -0
- data/lib/dragonfly/generation/hash_with_css_style_keys.rb +23 -0
- data/lib/dragonfly/generation/image_magick_generator.rb +140 -0
- data/lib/dragonfly/generation/r_magick_generator.rb +155 -0
- data/lib/dragonfly/generator.rb +9 -0
- data/lib/dragonfly/image_magick_utils.rb +81 -0
- data/lib/dragonfly/job.rb +371 -0
- data/lib/dragonfly/job_builder.rb +39 -0
- data/lib/dragonfly/job_definitions.rb +26 -0
- data/lib/dragonfly/job_endpoint.rb +15 -0
- data/lib/dragonfly/loggable.rb +28 -0
- data/lib/dragonfly/middleware.rb +34 -0
- data/lib/dragonfly/processing/image_magick_processor.rb +99 -0
- data/lib/dragonfly/processing/r_magick_processor.rb +126 -0
- data/lib/dragonfly/processor.rb +9 -0
- data/lib/dragonfly/r_magick_utils.rb +48 -0
- data/lib/dragonfly/rails/images.rb +22 -0
- data/lib/dragonfly/response.rb +82 -0
- data/lib/dragonfly/routed_endpoint.rb +40 -0
- data/lib/dragonfly/serializer.rb +32 -0
- data/lib/dragonfly/simple_cache.rb +23 -0
- data/lib/dragonfly/simple_endpoint.rb +63 -0
- data/lib/dragonfly/temp_object.rb +220 -0
- data/samples/beach.png +0 -0
- data/samples/egg.png +0 -0
- data/samples/round.gif +0 -0
- data/samples/sample.docx +0 -0
- data/samples/taj.jpg +0 -0
- data/spec/argument_matchers.rb +19 -0
- 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_model_extensions/model_spec.rb +723 -0
- 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 +57 -0
- data/spec/dragonfly/analysis/image_magick_analyser_spec.rb +15 -0
- data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +27 -0
- data/spec/dragonfly/analysis/shared_analyser_spec.rb +51 -0
- data/spec/dragonfly/app_spec.rb +280 -0
- data/spec/dragonfly/config/r_magick_spec.rb +25 -0
- data/spec/dragonfly/configurable_spec.rb +220 -0
- data/spec/dragonfly/core_ext/string_spec.rb +17 -0
- data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
- data/spec/dragonfly/data_storage/data_store_spec.rb +76 -0
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +169 -0
- data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +38 -0
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +94 -0
- data/spec/dragonfly/deprecation_spec.rb +20 -0
- data/spec/dragonfly/encoding/image_magick_encoder_spec.rb +41 -0
- data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +37 -0
- data/spec/dragonfly/function_manager_spec.rb +154 -0
- data/spec/dragonfly/generation/hash_with_css_style_keys_spec.rb +24 -0
- data/spec/dragonfly/generation/image_magick_generator_spec.rb +12 -0
- data/spec/dragonfly/generation/r_magick_generator_spec.rb +24 -0
- data/spec/dragonfly/generation/shared_generator_spec.rb +91 -0
- data/spec/dragonfly/image_magick_utils_spec.rb +16 -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 +120 -0
- data/spec/dragonfly/job_spec.rb +773 -0
- data/spec/dragonfly/loggable_spec.rb +80 -0
- data/spec/dragonfly/middleware_spec.rb +68 -0
- data/spec/dragonfly/processing/image_magick_processor_spec.rb +29 -0
- data/spec/dragonfly/processing/r_magick_processor_spec.rb +26 -0
- data/spec/dragonfly/processing/shared_processing_spec.rb +215 -0
- 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 +89 -0
- data/spec/dragonfly/temp_object_spec.rb +352 -0
- data/spec/image_matchers.rb +47 -0
- data/spec/simple_matchers.rb +44 -0
- data/spec/spec_helper.rb +58 -0
- data/yard/handlers/configurable_attr_handler.rb +38 -0
- data/yard/setup.rb +15 -0
- data/yard/templates/default/fulldoc/html/css/common.css +107 -0
- data/yard/templates/default/layout/html/layout.erb +87 -0
- data/yard/templates/default/module/html/configuration_summary.erb +31 -0
- data/yard/templates/default/module/setup.rb +17 -0
- 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,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
|