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.

Files changed (155) hide show
  1. data/{.specopts → .rspec} +0 -1
  2. data/.yardopts +6 -2
  3. data/Gemfile +14 -13
  4. data/History.md +47 -9
  5. data/README.md +25 -5
  6. data/Rakefile +37 -79
  7. data/VERSION +1 -1
  8. data/dragonfly.gemspec +140 -89
  9. data/extra_docs/Analysers.md +8 -48
  10. data/extra_docs/Configuration.md +40 -25
  11. data/extra_docs/Couch.md +49 -0
  12. data/extra_docs/DataStorage.md +94 -24
  13. data/extra_docs/Encoding.md +6 -35
  14. data/extra_docs/ExampleUseCases.md +113 -0
  15. data/extra_docs/GeneralUsage.md +7 -23
  16. data/extra_docs/Generators.md +15 -49
  17. data/extra_docs/Heroku.md +7 -8
  18. data/extra_docs/ImageMagick.md +126 -0
  19. data/extra_docs/MimeTypes.md +3 -3
  20. data/extra_docs/Models.md +163 -0
  21. data/extra_docs/Mongo.md +1 -4
  22. data/extra_docs/Processing.md +7 -60
  23. data/extra_docs/Rails2.md +3 -1
  24. data/extra_docs/Rails3.md +2 -10
  25. data/extra_docs/ServingRemotely.md +83 -0
  26. data/extra_docs/Sinatra.md +3 -3
  27. data/extra_docs/URLs.md +60 -33
  28. data/features/rails_3.0.5.feature +8 -0
  29. data/features/steps/rails_steps.rb +7 -18
  30. data/features/support/env.rb +10 -37
  31. data/features/support/setup.rb +32 -0
  32. data/fixtures/rails_3.0.5/files/app/models/album.rb +5 -0
  33. data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
  34. data/fixtures/{files → rails_3.0.5/files}/app/views/albums/show.html.erb +2 -0
  35. data/fixtures/{files → rails_3.0.5/files}/config/initializers/dragonfly.rb +0 -0
  36. data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
  37. data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
  38. data/fixtures/{files → rails_3.0.5/files}/features/step_definitions/image_steps.rb +11 -1
  39. data/fixtures/{files → rails_3.0.5/files}/features/support/paths.rb +2 -0
  40. data/fixtures/{files → rails_3.0.5/files}/features/text_images.feature +0 -0
  41. data/fixtures/{rails_3.0.3 → rails_3.0.5}/template.rb +2 -2
  42. data/irbrc.rb +2 -1
  43. data/lib/dragonfly.rb +7 -0
  44. data/lib/dragonfly/active_model_extensions/attachment.rb +134 -46
  45. data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +144 -0
  46. data/lib/dragonfly/active_model_extensions/class_methods.rb +62 -9
  47. data/lib/dragonfly/active_model_extensions/instance_methods.rb +2 -2
  48. data/lib/dragonfly/active_model_extensions/validations.rb +10 -6
  49. data/lib/dragonfly/analyser.rb +0 -1
  50. data/lib/dragonfly/analysis/file_command_analyser.rb +1 -1
  51. data/lib/dragonfly/analysis/image_magick_analyser.rb +2 -43
  52. data/lib/dragonfly/app.rb +64 -55
  53. data/lib/dragonfly/config/heroku.rb +1 -1
  54. data/lib/dragonfly/config/image_magick.rb +2 -37
  55. data/lib/dragonfly/config/rails.rb +5 -2
  56. data/lib/dragonfly/configurable.rb +115 -35
  57. data/lib/dragonfly/core_ext/object.rb +1 -1
  58. data/lib/dragonfly/core_ext/string.rb +1 -1
  59. data/lib/dragonfly/data_storage/couch_data_store.rb +84 -0
  60. data/lib/dragonfly/data_storage/file_data_store.rb +43 -18
  61. data/lib/dragonfly/data_storage/mongo_data_store.rb +8 -4
  62. data/lib/dragonfly/data_storage/s3data_store.rb +82 -38
  63. data/lib/dragonfly/encoding/image_magick_encoder.rb +2 -53
  64. data/lib/dragonfly/function_manager.rb +4 -2
  65. data/lib/dragonfly/generation/image_magick_generator.rb +2 -136
  66. data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
  67. data/lib/dragonfly/image_magick/analyser.rb +51 -0
  68. data/lib/dragonfly/image_magick/config.rb +44 -0
  69. data/lib/dragonfly/{encoding/r_magick_encoder.rb → image_magick/encoder.rb} +10 -14
  70. data/lib/dragonfly/image_magick/generator.rb +145 -0
  71. data/lib/dragonfly/image_magick/processor.rb +104 -0
  72. data/lib/dragonfly/image_magick/utils.rb +72 -0
  73. data/lib/dragonfly/image_magick_utils.rb +2 -79
  74. data/lib/dragonfly/job.rb +152 -90
  75. data/lib/dragonfly/middleware.rb +5 -19
  76. data/lib/dragonfly/processing/image_magick_processor.rb +2 -95
  77. data/lib/dragonfly/rails/images.rb +15 -10
  78. data/lib/dragonfly/response.rb +26 -12
  79. data/lib/dragonfly/serializer.rb +1 -4
  80. data/lib/dragonfly/server.rb +103 -0
  81. data/lib/dragonfly/temp_object.rb +56 -101
  82. data/lib/dragonfly/url_mapper.rb +78 -0
  83. data/spec/dragonfly/active_model_extensions/model_spec.rb +772 -65
  84. data/spec/dragonfly/active_model_extensions/spec_helper.rb +90 -10
  85. data/spec/dragonfly/analyser_spec.rb +1 -1
  86. data/spec/dragonfly/analysis/file_command_analyser_spec.rb +5 -14
  87. data/spec/dragonfly/app_spec.rb +35 -180
  88. data/spec/dragonfly/configurable_spec.rb +259 -18
  89. data/spec/dragonfly/core_ext/string_spec.rb +2 -2
  90. data/spec/dragonfly/core_ext/symbol_spec.rb +1 -1
  91. data/spec/dragonfly/data_storage/couch_data_store_spec.rb +84 -0
  92. data/spec/dragonfly/data_storage/file_data_store_spec.rb +149 -22
  93. data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +21 -2
  94. data/spec/dragonfly/data_storage/s3_data_store_spec.rb +207 -43
  95. data/spec/dragonfly/data_storage/{data_store_spec.rb → shared_data_store_examples.rb} +16 -15
  96. data/spec/dragonfly/function_manager_spec.rb +2 -2
  97. data/spec/dragonfly/{generation/hash_with_css_style_keys_spec.rb → hash_with_css_style_keys_spec.rb} +2 -2
  98. data/spec/dragonfly/{analysis/shared_analyser_spec.rb → image_magick/analyser_spec.rb} +19 -6
  99. data/spec/dragonfly/{encoding/image_magick_encoder_spec.rb → image_magick/encoder_spec.rb} +2 -2
  100. data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
  101. data/spec/dragonfly/{processing/shared_processing_spec.rb → image_magick/processor_spec.rb} +55 -6
  102. data/spec/dragonfly/image_magick/utils_spec.rb +18 -0
  103. data/spec/dragonfly/job_builder_spec.rb +1 -1
  104. data/spec/dragonfly/job_definitions_spec.rb +1 -1
  105. data/spec/dragonfly/job_endpoint_spec.rb +26 -3
  106. data/spec/dragonfly/job_spec.rb +426 -208
  107. data/spec/dragonfly/loggable_spec.rb +2 -2
  108. data/spec/dragonfly/middleware_spec.rb +5 -26
  109. data/spec/dragonfly/routed_endpoint_spec.rb +1 -1
  110. data/spec/dragonfly/serializer_spec.rb +1 -14
  111. data/spec/dragonfly/server_spec.rb +261 -0
  112. data/spec/dragonfly/simple_cache_spec.rb +1 -1
  113. data/spec/dragonfly/temp_object_spec.rb +84 -130
  114. data/spec/dragonfly/url_mapper_spec.rb +130 -0
  115. data/spec/functional/deprecations_spec.rb +51 -0
  116. data/spec/functional/image_magick_app_spec.rb +27 -0
  117. data/spec/functional/model_urls_spec.rb +85 -0
  118. data/spec/functional/remote_on_the_fly_spec.rb +51 -0
  119. data/spec/functional/to_response_spec.rb +31 -0
  120. data/spec/spec_helper.rb +12 -22
  121. data/spec/{argument_matchers.rb → support/argument_matchers.rb} +0 -0
  122. data/spec/{image_matchers.rb → support/image_matchers.rb} +4 -4
  123. data/spec/support/simple_matchers.rb +53 -0
  124. data/yard/handlers/configurable_attr_handler.rb +2 -2
  125. data/yard/templates/default/fulldoc/html/css/common.css +12 -10
  126. data/yard/templates/default/layout/html/layout.erb +6 -0
  127. metadata +267 -308
  128. data/Gemfile.rails.2.3.5 +0 -20
  129. data/features/3.0.3.feature +0 -8
  130. data/features/rails_2.3.5.feature +0 -7
  131. data/fixtures/files/app/models/album.rb +0 -3
  132. data/fixtures/files/app/views/albums/new.html.erb +0 -4
  133. data/fixtures/files/features/manage_album_images.feature +0 -12
  134. data/fixtures/rails_2.3.5/template.rb +0 -10
  135. data/lib/dragonfly/analysis/r_magick_analyser.rb +0 -63
  136. data/lib/dragonfly/config/r_magick.rb +0 -46
  137. data/lib/dragonfly/generation/hash_with_css_style_keys.rb +0 -23
  138. data/lib/dragonfly/generation/r_magick_generator.rb +0 -155
  139. data/lib/dragonfly/processing/r_magick_processor.rb +0 -126
  140. data/lib/dragonfly/r_magick_utils.rb +0 -48
  141. data/lib/dragonfly/simple_endpoint.rb +0 -76
  142. data/spec/dragonfly/active_model_extensions/active_model_setup.rb +0 -97
  143. data/spec/dragonfly/active_model_extensions/active_record_setup.rb +0 -85
  144. data/spec/dragonfly/analysis/image_magick_analyser_spec.rb +0 -15
  145. data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +0 -31
  146. data/spec/dragonfly/config/r_magick_spec.rb +0 -29
  147. data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +0 -41
  148. data/spec/dragonfly/generation/image_magick_generator_spec.rb +0 -12
  149. data/spec/dragonfly/generation/r_magick_generator_spec.rb +0 -28
  150. data/spec/dragonfly/generation/shared_generator_spec.rb +0 -91
  151. data/spec/dragonfly/image_magick_utils_spec.rb +0 -16
  152. data/spec/dragonfly/processing/image_magick_processor_spec.rb +0 -29
  153. data/spec/dragonfly/processing/r_magick_processor_spec.rb +0 -30
  154. data/spec/dragonfly/simple_endpoint_spec.rb +0 -97
  155. data/spec/simple_matchers.rb +0 -44
@@ -1,34 +1,20 @@
1
1
  module Dragonfly
2
2
  class Middleware
3
3
 
4
- def initialize(app, dragonfly_app_name, path_prefix)
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
- @endpoint = Rack::Builder.new {
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 = @endpoint.call(env)
15
- if route_not_found?(response)
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
- class ImageMagickProcessor
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
- app.define_macro(ActiveRecord::Base, :image_accessor)
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
- # Where the middleware is depends on the version of Rails
15
- middleware = Rails.respond_to?(:application) ? Rails.application.middleware : ActionController::Dispatcher.middleware
16
+ Rails.application.middleware.insert 0, 'Dragonfly::Middleware', :images
16
17
 
17
- middleware.insert_after 'Rack::Lock', 'Dragonfly::Middleware', :images, app.url_path_prefix
18
- middleware.insert_before 'Dragonfly::Middleware', 'Rack::Cache', {
19
- :verbose => true,
20
- :metastore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/meta"), # URI encoded because Windows
21
- :entitystore => URI.encode("file:#{Rails.root}/tmp/dragonfly/cache/body") # has problems with spaces
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
@@ -3,12 +3,9 @@ require 'uri'
3
3
  module Dragonfly
4
4
  class Response
5
5
 
6
- DEFAULT_FILENAME = proc{|job, request|
7
- if job.basename
8
- extname = job.encoded_extname || (".#{job.ext}" if job.ext)
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
- [200, success_headers.merge(cache_headers), []]
21
+ job.apply
22
+ [200, success_headers, []]
25
23
  elsif request.get?
26
- [200, success_headers.merge(cache_headers), job.result]
24
+ job.apply
25
+ [200, success_headers, job.result]
27
26
  end
28
27
  rescue DataStorage::DataNotFound => e
29
- [404, {"Content-Type" => 'text/plain'}, [e.message]]
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.resolve_mime_type,
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
@@ -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
- marshal_string = b64_decode(string)
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, opts={})
44
- opts ||= {} # in case it's nil
45
- initialize_from_object!(obj)
46
- validate_options!(opts)
47
- extract_attributes_from(opts)
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 ||= initialized_data || file{|f| f.read }
72
+ @data ||= file{|f| f.read }
52
73
  end
53
74
 
54
75
  def tempfile
55
76
  @tempfile ||= begin
56
- case initialized_with
57
- when :tempfile
58
- @tempfile = initialized_tempfile
59
- @tempfile.close
60
- when :data
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
- if initialized_data
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 initialized_data
119
- File.open(path, 'wb'){|f| f.write(initialized_data) }
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
- if initialized_data
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 initialized_with
150
- when :data
151
- data_string = size > 20 ? "#{initialized_data[0..20]}..." : initialized_data
129
+ content_string = case
130
+ when @data
131
+ data_string = size > 20 ? "#{@data[0..20]}..." : @data
152
132
  "data=#{data_string.inspect}"
153
- when :file then "file=#{initialized_file.inspect}"
154
- when :tempfile then "tempfile=#{initialized_tempfile.inspect}"
133
+ when @pathname then "pathname=#{@pathname.inspect}"
134
+ when @tempfile then "tempfile=#{@tempfile.inspect}"
155
135
  end
156
- to_s.sub(/>$/, " #{content_string}, @meta=#{@meta.inspect}, @name=#{@name.inspect} >")
136
+ to_s.sub(/>$/, " #{content_string} >")
157
137
  end
158
138
 
159
139
  protected
160
140
 
161
- attr_accessor :initialized_data, :initialized_tempfile, :initialized_file
162
-
163
- private
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 initialized_with
186
- if initialized_tempfile
187
- :tempfile
188
- elsif initialized_data
189
- :data
190
- elsif initialized_file
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 File.expand_path(path), tempfile.path
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