dragonfly 0.8.6 → 0.9.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/{.specopts → .rspec} +0 -1
- data/.yardopts +6 -2
- data/Gemfile +14 -13
- data/History.md +47 -9
- data/README.md +25 -5
- data/Rakefile +37 -79
- data/VERSION +1 -1
- data/dragonfly.gemspec +140 -89
- data/extra_docs/Analysers.md +8 -48
- data/extra_docs/Configuration.md +40 -25
- data/extra_docs/Couch.md +49 -0
- data/extra_docs/DataStorage.md +94 -24
- data/extra_docs/Encoding.md +6 -35
- data/extra_docs/ExampleUseCases.md +113 -0
- data/extra_docs/GeneralUsage.md +7 -23
- data/extra_docs/Generators.md +15 -49
- data/extra_docs/Heroku.md +7 -8
- data/extra_docs/ImageMagick.md +126 -0
- data/extra_docs/MimeTypes.md +3 -3
- data/extra_docs/Models.md +163 -0
- data/extra_docs/Mongo.md +1 -4
- data/extra_docs/Processing.md +7 -60
- data/extra_docs/Rails2.md +3 -1
- data/extra_docs/Rails3.md +2 -10
- data/extra_docs/ServingRemotely.md +83 -0
- data/extra_docs/Sinatra.md +3 -3
- data/extra_docs/URLs.md +60 -33
- data/features/rails_3.0.5.feature +8 -0
- data/features/steps/rails_steps.rb +7 -18
- data/features/support/env.rb +10 -37
- data/features/support/setup.rb +32 -0
- data/fixtures/rails_3.0.5/files/app/models/album.rb +5 -0
- data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
- data/fixtures/{files → rails_3.0.5/files}/app/views/albums/show.html.erb +2 -0
- data/fixtures/{files → rails_3.0.5/files}/config/initializers/dragonfly.rb +0 -0
- data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
- data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
- data/fixtures/{files → rails_3.0.5/files}/features/step_definitions/image_steps.rb +11 -1
- data/fixtures/{files → rails_3.0.5/files}/features/support/paths.rb +2 -0
- data/fixtures/{files → rails_3.0.5/files}/features/text_images.feature +0 -0
- data/fixtures/{rails_3.0.3 → rails_3.0.5}/template.rb +2 -2
- data/irbrc.rb +2 -1
- data/lib/dragonfly.rb +7 -0
- data/lib/dragonfly/active_model_extensions/attachment.rb +134 -46
- data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +144 -0
- data/lib/dragonfly/active_model_extensions/class_methods.rb +62 -9
- data/lib/dragonfly/active_model_extensions/instance_methods.rb +2 -2
- data/lib/dragonfly/active_model_extensions/validations.rb +10 -6
- data/lib/dragonfly/analyser.rb +0 -1
- data/lib/dragonfly/analysis/file_command_analyser.rb +1 -1
- data/lib/dragonfly/analysis/image_magick_analyser.rb +2 -43
- data/lib/dragonfly/app.rb +64 -55
- data/lib/dragonfly/config/heroku.rb +1 -1
- data/lib/dragonfly/config/image_magick.rb +2 -37
- data/lib/dragonfly/config/rails.rb +5 -2
- data/lib/dragonfly/configurable.rb +115 -35
- data/lib/dragonfly/core_ext/object.rb +1 -1
- data/lib/dragonfly/core_ext/string.rb +1 -1
- data/lib/dragonfly/data_storage/couch_data_store.rb +84 -0
- data/lib/dragonfly/data_storage/file_data_store.rb +43 -18
- data/lib/dragonfly/data_storage/mongo_data_store.rb +8 -4
- data/lib/dragonfly/data_storage/s3data_store.rb +82 -38
- data/lib/dragonfly/encoding/image_magick_encoder.rb +2 -53
- data/lib/dragonfly/function_manager.rb +4 -2
- data/lib/dragonfly/generation/image_magick_generator.rb +2 -136
- data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
- data/lib/dragonfly/image_magick/analyser.rb +51 -0
- data/lib/dragonfly/image_magick/config.rb +44 -0
- data/lib/dragonfly/{encoding/r_magick_encoder.rb → image_magick/encoder.rb} +10 -14
- data/lib/dragonfly/image_magick/generator.rb +145 -0
- data/lib/dragonfly/image_magick/processor.rb +104 -0
- data/lib/dragonfly/image_magick/utils.rb +72 -0
- data/lib/dragonfly/image_magick_utils.rb +2 -79
- data/lib/dragonfly/job.rb +152 -90
- data/lib/dragonfly/middleware.rb +5 -19
- data/lib/dragonfly/processing/image_magick_processor.rb +2 -95
- data/lib/dragonfly/rails/images.rb +15 -10
- data/lib/dragonfly/response.rb +26 -12
- data/lib/dragonfly/serializer.rb +1 -4
- data/lib/dragonfly/server.rb +103 -0
- data/lib/dragonfly/temp_object.rb +56 -101
- data/lib/dragonfly/url_mapper.rb +78 -0
- data/spec/dragonfly/active_model_extensions/model_spec.rb +772 -65
- data/spec/dragonfly/active_model_extensions/spec_helper.rb +90 -10
- data/spec/dragonfly/analyser_spec.rb +1 -1
- data/spec/dragonfly/analysis/file_command_analyser_spec.rb +5 -14
- data/spec/dragonfly/app_spec.rb +35 -180
- data/spec/dragonfly/configurable_spec.rb +259 -18
- data/spec/dragonfly/core_ext/string_spec.rb +2 -2
- data/spec/dragonfly/core_ext/symbol_spec.rb +1 -1
- data/spec/dragonfly/data_storage/couch_data_store_spec.rb +84 -0
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +149 -22
- data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +21 -2
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +207 -43
- data/spec/dragonfly/data_storage/{data_store_spec.rb → shared_data_store_examples.rb} +16 -15
- data/spec/dragonfly/function_manager_spec.rb +2 -2
- data/spec/dragonfly/{generation/hash_with_css_style_keys_spec.rb → hash_with_css_style_keys_spec.rb} +2 -2
- data/spec/dragonfly/{analysis/shared_analyser_spec.rb → image_magick/analyser_spec.rb} +19 -6
- data/spec/dragonfly/{encoding/image_magick_encoder_spec.rb → image_magick/encoder_spec.rb} +2 -2
- data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
- data/spec/dragonfly/{processing/shared_processing_spec.rb → image_magick/processor_spec.rb} +55 -6
- data/spec/dragonfly/image_magick/utils_spec.rb +18 -0
- data/spec/dragonfly/job_builder_spec.rb +1 -1
- data/spec/dragonfly/job_definitions_spec.rb +1 -1
- data/spec/dragonfly/job_endpoint_spec.rb +26 -3
- data/spec/dragonfly/job_spec.rb +426 -208
- data/spec/dragonfly/loggable_spec.rb +2 -2
- data/spec/dragonfly/middleware_spec.rb +5 -26
- data/spec/dragonfly/routed_endpoint_spec.rb +1 -1
- data/spec/dragonfly/serializer_spec.rb +1 -14
- data/spec/dragonfly/server_spec.rb +261 -0
- data/spec/dragonfly/simple_cache_spec.rb +1 -1
- data/spec/dragonfly/temp_object_spec.rb +84 -130
- data/spec/dragonfly/url_mapper_spec.rb +130 -0
- data/spec/functional/deprecations_spec.rb +51 -0
- data/spec/functional/image_magick_app_spec.rb +27 -0
- data/spec/functional/model_urls_spec.rb +85 -0
- data/spec/functional/remote_on_the_fly_spec.rb +51 -0
- data/spec/functional/to_response_spec.rb +31 -0
- data/spec/spec_helper.rb +12 -22
- data/spec/{argument_matchers.rb → support/argument_matchers.rb} +0 -0
- data/spec/{image_matchers.rb → support/image_matchers.rb} +4 -4
- data/spec/support/simple_matchers.rb +53 -0
- data/yard/handlers/configurable_attr_handler.rb +2 -2
- data/yard/templates/default/fulldoc/html/css/common.css +12 -10
- data/yard/templates/default/layout/html/layout.erb +6 -0
- metadata +267 -308
- data/Gemfile.rails.2.3.5 +0 -20
- data/features/3.0.3.feature +0 -8
- data/features/rails_2.3.5.feature +0 -7
- data/fixtures/files/app/models/album.rb +0 -3
- data/fixtures/files/app/views/albums/new.html.erb +0 -4
- data/fixtures/files/features/manage_album_images.feature +0 -12
- data/fixtures/rails_2.3.5/template.rb +0 -10
- data/lib/dragonfly/analysis/r_magick_analyser.rb +0 -63
- data/lib/dragonfly/config/r_magick.rb +0 -46
- data/lib/dragonfly/generation/hash_with_css_style_keys.rb +0 -23
- data/lib/dragonfly/generation/r_magick_generator.rb +0 -155
- data/lib/dragonfly/processing/r_magick_processor.rb +0 -126
- data/lib/dragonfly/r_magick_utils.rb +0 -48
- data/lib/dragonfly/simple_endpoint.rb +0 -76
- data/spec/dragonfly/active_model_extensions/active_model_setup.rb +0 -97
- data/spec/dragonfly/active_model_extensions/active_record_setup.rb +0 -85
- data/spec/dragonfly/analysis/image_magick_analyser_spec.rb +0 -15
- data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +0 -31
- data/spec/dragonfly/config/r_magick_spec.rb +0 -29
- data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +0 -41
- data/spec/dragonfly/generation/image_magick_generator_spec.rb +0 -12
- data/spec/dragonfly/generation/r_magick_generator_spec.rb +0 -28
- data/spec/dragonfly/generation/shared_generator_spec.rb +0 -91
- data/spec/dragonfly/image_magick_utils_spec.rb +0 -16
- data/spec/dragonfly/processing/image_magick_processor_spec.rb +0 -29
- data/spec/dragonfly/processing/r_magick_processor_spec.rb +0 -30
- data/spec/dragonfly/simple_endpoint_spec.rb +0 -97
- data/spec/simple_matchers.rb +0 -44
data/lib/dragonfly/middleware.rb
CHANGED
@@ -1,34 +1,20 @@
|
|
1
1
|
module Dragonfly
|
2
2
|
class Middleware
|
3
3
|
|
4
|
-
def initialize(app, dragonfly_app_name,
|
4
|
+
def initialize(app, dragonfly_app_name, deprecated_arg=nil)
|
5
|
+
raise ArgumentError, "mounting Dragonfly::Middleware with a mount point is deprecated - just use Dragonfly::Middleware, #{dragonfly_app_name.inspect}" if deprecated_arg
|
5
6
|
@app = app
|
6
|
-
@
|
7
|
-
map path_prefix do
|
8
|
-
run Dragonfly[dragonfly_app_name]
|
9
|
-
end
|
10
|
-
}.to_app
|
7
|
+
@dragonfly_app_name = dragonfly_app_name
|
11
8
|
end
|
12
9
|
|
13
10
|
def call(env)
|
14
|
-
response = @
|
15
|
-
if
|
11
|
+
response = Dragonfly[@dragonfly_app_name].call(env)
|
12
|
+
if response[1]['X-Cascade'] == 'pass'
|
16
13
|
@app.call(env)
|
17
14
|
else
|
18
15
|
response
|
19
16
|
end
|
20
17
|
end
|
21
18
|
|
22
|
-
private
|
23
|
-
|
24
|
-
def route_not_found?(response)
|
25
|
-
response[1]['X-Cascade'] == 'pass' ||
|
26
|
-
(rack_version_doesnt_support_x_cascade? && response[0] == 404)
|
27
|
-
end
|
28
|
-
|
29
|
-
def rack_version_doesnt_support_x_cascade?
|
30
|
-
Rack.version < '1.1'
|
31
|
-
end
|
32
|
-
|
33
19
|
end
|
34
20
|
end
|
@@ -1,99 +1,6 @@
|
|
1
1
|
module Dragonfly
|
2
2
|
module Processing
|
3
|
-
|
4
|
-
|
5
|
-
GRAVITIES = {
|
6
|
-
'nw' => 'NorthWest',
|
7
|
-
'n' => 'North',
|
8
|
-
'ne' => 'NorthEast',
|
9
|
-
'w' => 'West',
|
10
|
-
'c' => 'Center',
|
11
|
-
'e' => 'East',
|
12
|
-
'sw' => 'SouthWest',
|
13
|
-
's' => 'South',
|
14
|
-
'se' => 'SouthEast'
|
15
|
-
}
|
16
|
-
|
17
|
-
# Geometry string patterns
|
18
|
-
RESIZE_GEOMETRY = /^\d*x\d*[><%^!]?$|^\d+@$/ # e.g. '300x200!'
|
19
|
-
CROPPED_RESIZE_GEOMETRY = /^(\d+)x(\d+)#(\w{1,2})?$/ # e.g. '20x50#ne'
|
20
|
-
CROP_GEOMETRY = /^(\d+)x(\d+)([+-]\d+)?([+-]\d+)?(\w{1,2})?$/ # e.g. '30x30+10+10'
|
21
|
-
THUMB_GEOMETRY = Regexp.union RESIZE_GEOMETRY, CROPPED_RESIZE_GEOMETRY, CROP_GEOMETRY
|
22
|
-
|
23
|
-
include ImageMagickUtils
|
24
|
-
|
25
|
-
def resize(temp_object, geometry)
|
26
|
-
convert(temp_object, "-resize '#{geometry}'")
|
27
|
-
end
|
28
|
-
|
29
|
-
def crop(temp_object, opts={})
|
30
|
-
width = opts[:width]
|
31
|
-
height = opts[:height]
|
32
|
-
gravity = GRAVITIES[opts[:gravity]]
|
33
|
-
x = "#{opts[:x] || 0}"
|
34
|
-
x = '+' + x unless x[/^[+-]/]
|
35
|
-
y = "#{opts[:y] || 0}"
|
36
|
-
y = '+' + y unless y[/^[+-]/]
|
37
|
-
|
38
|
-
convert(temp_object, "-crop #{width}x#{height}#{x}#{y}#{" -gravity #{gravity}" if gravity}")
|
39
|
-
end
|
40
|
-
|
41
|
-
def flip(temp_object)
|
42
|
-
convert(temp_object, "-flip")
|
43
|
-
end
|
44
|
-
|
45
|
-
def flop(temp_object)
|
46
|
-
convert(temp_object, "-flop")
|
47
|
-
end
|
48
|
-
|
49
|
-
def greyscale(temp_object)
|
50
|
-
convert(temp_object, "-colorspace Gray")
|
51
|
-
end
|
52
|
-
alias grayscale greyscale
|
53
|
-
|
54
|
-
def resize_and_crop(temp_object, opts={})
|
55
|
-
attrs = identify(temp_object)
|
56
|
-
current_width = attrs[:width].to_i
|
57
|
-
current_height = attrs[:height].to_i
|
58
|
-
|
59
|
-
width = opts[:width] ? opts[:width].to_i : current_width
|
60
|
-
height = opts[:height] ? opts[:height].to_i : current_height
|
61
|
-
gravity = opts[:gravity] || 'c'
|
62
|
-
|
63
|
-
if width != current_width || height != current_height
|
64
|
-
scale = [width.to_f / current_width, height.to_f / current_height].max
|
65
|
-
temp_object = TempObject.new(resize(temp_object, "#{(scale * current_width).ceil}x#{(scale * current_height).ceil}"))
|
66
|
-
end
|
67
|
-
|
68
|
-
crop(temp_object, :width => width, :height => height, :gravity => gravity)
|
69
|
-
end
|
70
|
-
|
71
|
-
def rotate(temp_object, amount, opts={})
|
72
|
-
convert(temp_object, "-rotate '#{amount}#{opts[:qualifier]}'")
|
73
|
-
end
|
74
|
-
|
75
|
-
def thumb(temp_object, geometry)
|
76
|
-
case geometry
|
77
|
-
when RESIZE_GEOMETRY
|
78
|
-
resize(temp_object, geometry)
|
79
|
-
when CROPPED_RESIZE_GEOMETRY
|
80
|
-
resize_and_crop(temp_object, :width => $1, :height => $2, :gravity => $3)
|
81
|
-
when CROP_GEOMETRY
|
82
|
-
crop(temp_object,
|
83
|
-
:width => $1,
|
84
|
-
:height => $2,
|
85
|
-
:x => $3,
|
86
|
-
:y => $4,
|
87
|
-
:gravity => $5
|
88
|
-
)
|
89
|
-
else raise ArgumentError, "Didn't recognise the geometry string #{geometry}"
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
def convert(temp_object, args='', format=nil)
|
94
|
-
format ? [super, {:format => format.to_sym}] : super
|
95
|
-
end
|
96
|
-
|
97
|
-
end
|
3
|
+
puts "WARNING: Dragonfly::Processing::ImageMagickProcessor is DEPRECATED and will soon be removed. Please use Dragonfly::ImageMagick::Processor instead."
|
4
|
+
ImageMagickProcessor = ImageMagick::Processor
|
98
5
|
end
|
99
6
|
end
|
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'dragonfly'
|
2
|
-
require 'rack/cache'
|
3
2
|
require 'uri'
|
4
3
|
|
5
4
|
### The dragonfly app ###
|
@@ -8,15 +7,21 @@ app.configure_with(:rails)
|
|
8
7
|
app.configure_with(:imagemagick)
|
9
8
|
|
10
9
|
### Extend active record ###
|
11
|
-
|
10
|
+
if defined?(ActiveRecord::Base)
|
11
|
+
app.define_macro(ActiveRecord::Base, :image_accessor)
|
12
|
+
app.define_macro(ActiveRecord::Base, :file_accessor)
|
13
|
+
end
|
12
14
|
|
13
15
|
### Insert the middleware ###
|
14
|
-
|
15
|
-
middleware = Rails.respond_to?(:application) ? Rails.application.middleware : ActionController::Dispatcher.middleware
|
16
|
+
Rails.application.middleware.insert 0, 'Dragonfly::Middleware', :images
|
16
17
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
}
|
18
|
+
begin
|
19
|
+
require 'rack/cache'
|
20
|
+
Rails.application.middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
|
21
|
+
:verbose => true,
|
22
|
+
:metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded because Windows
|
23
|
+
:entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body") # has problems with spaces
|
24
|
+
}
|
25
|
+
rescue LoadError => e
|
26
|
+
app.log.warn("Warning: couldn't find rack-cache for caching dragonfly content")
|
27
|
+
end
|
data/lib/dragonfly/response.rb
CHANGED
@@ -3,12 +3,9 @@ require 'uri'
|
|
3
3
|
module Dragonfly
|
4
4
|
class Response
|
5
5
|
|
6
|
-
DEFAULT_FILENAME = proc
|
7
|
-
if job.basename
|
8
|
-
|
9
|
-
"#{job.basename}#{extname}"
|
10
|
-
end
|
11
|
-
}
|
6
|
+
DEFAULT_FILENAME = proc do |job, request|
|
7
|
+
[job.basename, job.format].compact.join('.') if job.basename
|
8
|
+
end
|
12
9
|
|
13
10
|
def initialize(job, env)
|
14
11
|
@job, @env = job, env
|
@@ -21,12 +18,19 @@ module Dragonfly
|
|
21
18
|
elsif etag_matches?
|
22
19
|
[304, cache_headers, []]
|
23
20
|
elsif request.head?
|
24
|
-
|
21
|
+
job.apply
|
22
|
+
[200, success_headers, []]
|
25
23
|
elsif request.get?
|
26
|
-
|
24
|
+
job.apply
|
25
|
+
[200, success_headers, job.result]
|
27
26
|
end
|
28
27
|
rescue DataStorage::DataNotFound => e
|
29
|
-
|
28
|
+
app.log.warn(e.message)
|
29
|
+
[404, {"Content-Type" => 'text/plain'}, ['Not found']]
|
30
|
+
end
|
31
|
+
|
32
|
+
def will_be_served?
|
33
|
+
request.get? && !etag_matches?
|
30
34
|
end
|
31
35
|
|
32
36
|
private
|
@@ -45,8 +49,9 @@ module Dragonfly
|
|
45
49
|
end
|
46
50
|
|
47
51
|
def etag_matches?
|
52
|
+
return @etag_matches unless @etag_matches.nil?
|
48
53
|
if_none_match = env['HTTP_IF_NONE_MATCH']
|
49
|
-
if if_none_match
|
54
|
+
@etag_matches = if if_none_match
|
50
55
|
if_none_match.tr!('"','')
|
51
56
|
if_none_match.split(',').include?(job.unique_signature) || if_none_match == '*'
|
52
57
|
else
|
@@ -56,9 +61,11 @@ module Dragonfly
|
|
56
61
|
|
57
62
|
def success_headers
|
58
63
|
{
|
59
|
-
"Content-Type" => job.
|
64
|
+
"Content-Type" => job.mime_type,
|
60
65
|
"Content-Length" => job.size.to_s
|
61
|
-
}.merge(content_disposition_header)
|
66
|
+
}.merge(content_disposition_header).
|
67
|
+
merge(cache_headers).
|
68
|
+
merge(custom_headers)
|
62
69
|
end
|
63
70
|
|
64
71
|
def content_disposition_header
|
@@ -83,6 +90,13 @@ module Dragonfly
|
|
83
90
|
@filename ||= evaluate(app.content_filename)
|
84
91
|
end
|
85
92
|
|
93
|
+
def custom_headers
|
94
|
+
@custom_headers ||= app.response_headers.inject({}) do |headers, (k, v)|
|
95
|
+
headers[k] = evaluate(v)
|
96
|
+
headers
|
97
|
+
end
|
98
|
+
end
|
99
|
+
|
86
100
|
def evaluate(attribute)
|
87
101
|
attribute.respond_to?(:call) ? attribute.call(job, request) : attribute
|
88
102
|
end
|
data/lib/dragonfly/serializer.rb
CHANGED
@@ -6,7 +6,6 @@ module Dragonfly
|
|
6
6
|
|
7
7
|
# Exceptions
|
8
8
|
class BadString < RuntimeError; end
|
9
|
-
class MaliciousString < RuntimeError; end
|
10
9
|
|
11
10
|
extend self # So we can do Serializer.b64_encode, etc.
|
12
11
|
|
@@ -24,9 +23,7 @@ module Dragonfly
|
|
24
23
|
end
|
25
24
|
|
26
25
|
def marshal_decode(string)
|
27
|
-
|
28
|
-
raise MaliciousString, "potentially malicious marshal string #{marshal_string.inspect}" if marshal_string[/@[a-z_]/i]
|
29
|
-
Marshal.load(marshal_string)
|
26
|
+
Marshal.load(b64_decode(string))
|
30
27
|
rescue TypeError, ArgumentError => e
|
31
28
|
raise BadString, "couldn't decode #{string} - got #{e}"
|
32
29
|
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
class Server
|
3
|
+
|
4
|
+
include Loggable
|
5
|
+
include Configurable
|
6
|
+
|
7
|
+
configurable_attr :dragonfly_url, '/dragonfly'
|
8
|
+
configurable_attr :protect_from_dos_attacks, false
|
9
|
+
configurable_attr :url_format, '/:job/:basename.:format'
|
10
|
+
configurable_attr :url_host
|
11
|
+
|
12
|
+
extend Forwardable
|
13
|
+
def_delegator :url_mapper, :params_in_url
|
14
|
+
|
15
|
+
def initialize(app)
|
16
|
+
@app = app
|
17
|
+
use_same_log_as(app)
|
18
|
+
use_as_fallback_config(app)
|
19
|
+
end
|
20
|
+
|
21
|
+
def before_serve(&block)
|
22
|
+
self.before_serve_callback = block
|
23
|
+
end
|
24
|
+
configuration_method :before_serve
|
25
|
+
|
26
|
+
def call(env)
|
27
|
+
if dragonfly_url == env["PATH_INFO"]
|
28
|
+
dragonfly_response
|
29
|
+
elsif (params = url_mapper.params_for(env["PATH_INFO"], env["QUERY_STRING"])) && params['job']
|
30
|
+
job = Job.deserialize(params['job'], app)
|
31
|
+
job.validate_sha!(params['sha']) if protect_from_dos_attacks
|
32
|
+
response = Response.new(job, env)
|
33
|
+
catch(:halt) do
|
34
|
+
if before_serve_callback && response.will_be_served?
|
35
|
+
before_serve_callback.call(job, env)
|
36
|
+
end
|
37
|
+
response.to_response
|
38
|
+
end
|
39
|
+
else
|
40
|
+
[404, {'Content-Type' => 'text/plain', 'X-Cascade' => 'pass'}, ['Not found']]
|
41
|
+
end
|
42
|
+
rescue Serializer::BadString, Job::InvalidArray => e
|
43
|
+
log.warn(e.message)
|
44
|
+
[404, {'Content-Type' => 'text/plain'}, ['Not found']]
|
45
|
+
rescue Job::NoSHAGiven => e
|
46
|
+
[400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
|
47
|
+
rescue Job::IncorrectSHA => e
|
48
|
+
[400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
|
49
|
+
end
|
50
|
+
|
51
|
+
def url_for(job, opts={})
|
52
|
+
opts = opts.dup
|
53
|
+
host = opts.delete(:host) || url_host
|
54
|
+
params = stringify_keys(opts)
|
55
|
+
params['job'] = job.serialize
|
56
|
+
params['sha'] = job.sha if protect_from_dos_attacks
|
57
|
+
url = url_mapper.url_for(params)
|
58
|
+
"#{host}#{url}"
|
59
|
+
end
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
attr_reader :app
|
64
|
+
attr_accessor :before_serve_callback
|
65
|
+
|
66
|
+
def url_mapper
|
67
|
+
@url_mapper ||= UrlMapper.new(url_format,
|
68
|
+
:job => '[\w+]',
|
69
|
+
:basename => '[^\/]',
|
70
|
+
:format => '[^\.]'
|
71
|
+
)
|
72
|
+
end
|
73
|
+
|
74
|
+
def stringify_keys(params)
|
75
|
+
params.inject({}) do |hash, (k, v)|
|
76
|
+
hash[k.to_s] = v
|
77
|
+
hash
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
def dragonfly_response
|
82
|
+
body = <<-DRAGONFLY
|
83
|
+
_o|o_
|
84
|
+
_~~---._( )_.---~~_
|
85
|
+
( . \\ / . )
|
86
|
+
`-.~--' |=| '--~.-'
|
87
|
+
_~-.~'" /|=|\\ "'~.-~_
|
88
|
+
( ./ |=| \\. )
|
89
|
+
`~~`"` |=| `"'ME"
|
90
|
+
|-|
|
91
|
+
<->
|
92
|
+
V
|
93
|
+
DRAGONFLY
|
94
|
+
[200, {
|
95
|
+
'Content-Type' => 'text/plain',
|
96
|
+
'Content-Size' => body.bytesize.to_s
|
97
|
+
},
|
98
|
+
[body]
|
99
|
+
]
|
100
|
+
end
|
101
|
+
|
102
|
+
end
|
103
|
+
end
|
@@ -1,5 +1,6 @@
|
|
1
1
|
require 'stringio'
|
2
2
|
require 'tempfile'
|
3
|
+
require 'pathname'
|
3
4
|
|
4
5
|
module Dragonfly
|
5
6
|
|
@@ -10,6 +11,7 @@ module Dragonfly
|
|
10
11
|
# You can initialize it various ways:
|
11
12
|
#
|
12
13
|
# temp_object = Dragonfly::TempObject.new('this is the content') # with a String
|
14
|
+
# temp_object = Dragonfly::TempObject.new(Pathname.new('path/to/content')) # with a Pathname
|
13
15
|
# temp_object = Dragonfly::TempObject.new(File.new('path/to/content')) # with a File
|
14
16
|
# temp_object = Dragonfly::TempObject.new(some_tempfile) # with a Tempfile
|
15
17
|
# temp_object = Dragonfly::TempObject.new(some_other_temp_object) # with another TempObject
|
@@ -40,27 +42,43 @@ module Dragonfly
|
|
40
42
|
|
41
43
|
# Instance Methods
|
42
44
|
|
43
|
-
def initialize(obj
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
45
|
+
def initialize(obj)
|
46
|
+
if obj.is_a? TempObject
|
47
|
+
@data = obj.get_data
|
48
|
+
@tempfile = obj.get_tempfile
|
49
|
+
@pathname = obj.get_pathname
|
50
|
+
elsif obj.is_a? String
|
51
|
+
@data = obj
|
52
|
+
elsif obj.is_a? Tempfile
|
53
|
+
@tempfile = obj
|
54
|
+
elsif obj.is_a? File
|
55
|
+
@pathname = Pathname.new(obj.path)
|
56
|
+
@original_filename = @pathname.basename.to_s
|
57
|
+
elsif obj.is_a? Pathname
|
58
|
+
@pathname = obj
|
59
|
+
@original_filename = @pathname.basename.to_s
|
60
|
+
elsif obj.respond_to?(:tempfile)
|
61
|
+
@tempfile = obj.tempfile
|
62
|
+
else
|
63
|
+
raise ArgumentError, "#{self.class.name} must be initialized with a String, a Pathname, a File, a Tempfile, another TempObject, or something that responds to .tempfile"
|
64
|
+
end
|
65
|
+
@tempfile.close if @tempfile
|
66
|
+
@original_filename = obj.original_filename if obj.respond_to?(:original_filename)
|
48
67
|
end
|
68
|
+
|
69
|
+
attr_reader :original_filename
|
49
70
|
|
50
71
|
def data
|
51
|
-
@data ||=
|
72
|
+
@data ||= file{|f| f.read }
|
52
73
|
end
|
53
74
|
|
54
75
|
def tempfile
|
55
76
|
@tempfile ||= begin
|
56
|
-
case
|
57
|
-
when
|
58
|
-
@tempfile =
|
59
|
-
|
60
|
-
|
61
|
-
@tempfile = new_tempfile(initialized_data)
|
62
|
-
when :file
|
63
|
-
@tempfile = copy_to_tempfile(initialized_file.path)
|
77
|
+
case
|
78
|
+
when @data
|
79
|
+
@tempfile = new_tempfile(@data)
|
80
|
+
when @pathname
|
81
|
+
@tempfile = copy_to_tempfile(@pathname.expand_path)
|
64
82
|
end
|
65
83
|
@tempfile
|
66
84
|
end
|
@@ -79,31 +97,11 @@ module Dragonfly
|
|
79
97
|
end
|
80
98
|
|
81
99
|
def path
|
82
|
-
tempfile.path
|
100
|
+
@pathname ? @pathname.expand_path.to_s : tempfile.path
|
83
101
|
end
|
84
102
|
|
85
103
|
def size
|
86
|
-
|
87
|
-
initialized_data.bytesize
|
88
|
-
else
|
89
|
-
File.size(path)
|
90
|
-
end
|
91
|
-
end
|
92
|
-
|
93
|
-
attr_accessor :name, :format
|
94
|
-
alias _format format
|
95
|
-
attr_writer :meta
|
96
|
-
|
97
|
-
def meta
|
98
|
-
@meta ||= {}
|
99
|
-
end
|
100
|
-
|
101
|
-
def basename
|
102
|
-
File.basename(name, '.*') if name
|
103
|
-
end
|
104
|
-
|
105
|
-
def ext
|
106
|
-
File.extname(name)[/\.(.*)/, 1] if name
|
104
|
+
@data ? @data.bytesize : File.size(path)
|
107
105
|
end
|
108
106
|
|
109
107
|
def each(&block)
|
@@ -115,8 +113,8 @@ module Dragonfly
|
|
115
113
|
end
|
116
114
|
|
117
115
|
def to_file(path)
|
118
|
-
if
|
119
|
-
File.open(path, 'wb'){|f| f.write(
|
116
|
+
if @data
|
117
|
+
File.open(path, 'wb'){|f| f.write(@data) }
|
120
118
|
else
|
121
119
|
FileUtils.cp(self.path, path)
|
122
120
|
end
|
@@ -124,90 +122,47 @@ module Dragonfly
|
|
124
122
|
end
|
125
123
|
|
126
124
|
def to_io(&block)
|
127
|
-
|
128
|
-
StringIO.open(initialized_data, 'rb', &block)
|
129
|
-
else
|
130
|
-
file(&block)
|
131
|
-
end
|
132
|
-
end
|
133
|
-
|
134
|
-
def attributes
|
135
|
-
{
|
136
|
-
:name => name,
|
137
|
-
:meta => meta,
|
138
|
-
:format => format
|
139
|
-
}
|
140
|
-
end
|
141
|
-
|
142
|
-
def extract_attributes_from(hash)
|
143
|
-
self.name = hash.delete(:name) unless hash[:name].blank?
|
144
|
-
self.format = hash.delete(:format) unless hash[:format].blank?
|
145
|
-
self.meta.merge!(hash.delete(:meta)) unless hash[:meta].blank?
|
125
|
+
@data ? StringIO.open(@data, 'rb', &block) : file(&block)
|
146
126
|
end
|
147
127
|
|
148
128
|
def inspect
|
149
|
-
content_string = case
|
150
|
-
when
|
151
|
-
data_string = size > 20 ? "#{
|
129
|
+
content_string = case
|
130
|
+
when @data
|
131
|
+
data_string = size > 20 ? "#{@data[0..20]}..." : @data
|
152
132
|
"data=#{data_string.inspect}"
|
153
|
-
when
|
154
|
-
when
|
133
|
+
when @pathname then "pathname=#{@pathname.inspect}"
|
134
|
+
when @tempfile then "tempfile=#{@tempfile.inspect}"
|
155
135
|
end
|
156
|
-
to_s.sub(/>$/, " #{content_string}
|
136
|
+
to_s.sub(/>$/, " #{content_string} >")
|
157
137
|
end
|
158
138
|
|
159
139
|
protected
|
160
140
|
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
def initialize_from_object!(obj)
|
166
|
-
if obj.is_a? TempObject
|
167
|
-
@initialized_data = obj.initialized_data
|
168
|
-
@initialized_tempfile = copy_to_tempfile(obj.initialized_tempfile.path) if obj.initialized_tempfile
|
169
|
-
@initialized_file = obj.initialized_file
|
170
|
-
elsif obj.is_a? String
|
171
|
-
@initialized_data = obj
|
172
|
-
elsif obj.is_a? Tempfile
|
173
|
-
@initialized_tempfile = obj
|
174
|
-
elsif obj.is_a? File
|
175
|
-
@initialized_file = obj
|
176
|
-
self.name = File.basename(obj.path)
|
177
|
-
elsif obj.respond_to?(:tempfile)
|
178
|
-
@initialized_tempfile = obj.tempfile
|
179
|
-
else
|
180
|
-
raise ArgumentError, "#{self.class.name} must be initialized with a String, a File, a Tempfile, another TempObject, or something that responds to .tempfile"
|
181
|
-
end
|
182
|
-
self.name = obj.original_filename if obj.respond_to?(:original_filename)
|
141
|
+
# We don't use normal accessors here because #data etc. do more than just return the instance var
|
142
|
+
def get_data
|
143
|
+
@data
|
183
144
|
end
|
184
|
-
|
185
|
-
def
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
:file
|
192
|
-
end
|
145
|
+
|
146
|
+
def get_pathname
|
147
|
+
@pathname
|
148
|
+
end
|
149
|
+
|
150
|
+
def get_tempfile
|
151
|
+
@tempfile
|
193
152
|
end
|
194
153
|
|
154
|
+
private
|
155
|
+
|
195
156
|
def block_size
|
196
157
|
self.class.block_size
|
197
158
|
end
|
198
159
|
|
199
160
|
def copy_to_tempfile(path)
|
200
161
|
tempfile = new_tempfile
|
201
|
-
FileUtils.cp
|
162
|
+
FileUtils.cp path, tempfile.path
|
202
163
|
tempfile
|
203
164
|
end
|
204
165
|
|
205
|
-
def validate_options!(opts)
|
206
|
-
valid_keys = [:name, :meta, :format]
|
207
|
-
invalid_keys = opts.keys - valid_keys
|
208
|
-
raise ArgumentError, "Unrecognised options #{invalid_keys.inspect}" if invalid_keys.any?
|
209
|
-
end
|
210
|
-
|
211
166
|
def new_tempfile(content=nil)
|
212
167
|
tempfile = Tempfile.new('dragonfly')
|
213
168
|
tempfile.binmode
|