dragonfly 0.7.5 → 0.7.6

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.

@@ -11,6 +11,10 @@ module Dragonfly
11
11
  configurable_attr :port
12
12
  configurable_attr :database, 'dragonfly'
13
13
 
14
+ # Mongo gem deprecated ObjectID in favour of ObjectId
15
+ OBJECT_ID = defined?(BSON::ObjectId) ? BSON::ObjectId : BSON::ObjectID
16
+ INVALID_OBJECT_ID = defined?(BSON::InvalidObjectId) ? BSON::InvalidObjectId : BSON::InvalidObjectID
17
+
14
18
  def initialize(opts={})
15
19
  self.host = opts[:host]
16
20
  self.port = opts[:port]
@@ -32,13 +36,13 @@ module Dragonfly
32
36
  grid_io.read,
33
37
  extra
34
38
  ]
35
- rescue Mongo::GridFileNotFound, BSON::InvalidObjectID => e
39
+ rescue Mongo::GridFileNotFound, INVALID_OBJECT_ID => e
36
40
  raise DataNotFound, "#{e} - #{uid}"
37
41
  end
38
42
 
39
43
  def destroy(uid)
40
44
  grid.delete(bson_id(uid))
41
- rescue Mongo::GridFileNotFound, BSON::InvalidObjectID => e
45
+ rescue Mongo::GridFileNotFound, INVALID_OBJECT_ID => e
42
46
  raise DataNotFound, "#{e} - #{uid}"
43
47
  end
44
48
 
@@ -57,7 +61,7 @@ module Dragonfly
57
61
  end
58
62
 
59
63
  def bson_id(uid)
60
- BSON::ObjectID.from_string(uid)
64
+ OBJECT_ID.from_string(uid)
61
65
  end
62
66
 
63
67
  end
@@ -32,7 +32,7 @@ module Dragonfly
32
32
  end
33
33
 
34
34
  def store(temp_object, opts={})
35
- uid = generate_uid(temp_object.name || 'file')
35
+ uid = opts[:path] || generate_uid(temp_object.name || 'file')
36
36
  ensure_initialized
37
37
  object = use_filesystem ? temp_object.file : temp_object.data
38
38
  extra_data = temp_object.attributes
data/lib/dragonfly/job.rb CHANGED
@@ -1,5 +1,6 @@
1
1
  require 'forwardable'
2
2
  require 'digest/sha1'
3
+ require 'base64'
3
4
 
4
5
  module Dragonfly
5
6
  class Job
@@ -15,7 +16,7 @@ module Dragonfly
15
16
  class IncorrectSHA < StandardError; end
16
17
 
17
18
  extend Forwardable
18
- def_delegators :result, :data, :file, :tempfile, :path, :to_file, :size, :ext, :name, :meta, :format, :_format
19
+ def_delegators :result, :data, :file, :tempfile, :path, :to_file, :size, :ext, :name, :name=, :basename, :meta, :meta=, :format, :_format
19
20
 
20
21
  class Step
21
22
 
@@ -37,7 +38,15 @@ module Dragonfly
37
38
  def initialize(*args)
38
39
  @args = args
39
40
  end
41
+
40
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
+
41
50
  def inspect
42
51
  "#{self.class.step_name}(#{args.map{|a| a.inspect }.join(', ')})"
43
52
  end
@@ -49,7 +58,7 @@ module Dragonfly
49
58
  end
50
59
  def apply(job)
51
60
  content, extra = job.app.datastore.retrieve(uid)
52
- job.temp_object = TempObject.new(content, (extra || {}))
61
+ job.temp_object = TempObject.new(content, extra)
53
62
  end
54
63
  end
55
64
 
@@ -62,11 +71,8 @@ module Dragonfly
62
71
  end
63
72
  def apply(job)
64
73
  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
- )
74
+ content, extra = job.app.processor.process(job.temp_object, name, *arguments)
75
+ update_temp_object(job, content, extra)
70
76
  end
71
77
  end
72
78
 
@@ -79,18 +85,16 @@ module Dragonfly
79
85
  end
80
86
  def apply(job)
81
87
  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
- )
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
87
91
  end
88
92
  end
89
93
 
90
94
  class Generate < Step
91
95
  def apply(job)
92
96
  content, extra = job.app.generator.generate(*args)
93
- job.temp_object = TempObject.new(content, (extra || {}))
97
+ job.temp_object = TempObject.new(content, extra)
94
98
  end
95
99
  end
96
100
 
@@ -210,6 +214,8 @@ module Dragonfly
210
214
  new_job
211
215
  end
212
216
 
217
+ # Applying, etc.
218
+
213
219
  def apply
214
220
  pending_steps.each{|step| step.apply(self) }
215
221
  self.next_step_index = steps.length
@@ -235,6 +241,8 @@ module Dragonfly
235
241
  }
236
242
  end
237
243
 
244
+ # Serializing, etc.
245
+
238
246
  def serialize
239
247
  Serializer.marshal_encode(to_a)
240
248
  end
@@ -258,6 +266,18 @@ module Dragonfly
258
266
  end
259
267
  end
260
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
+
261
281
  def to_app
262
282
  JobEndpoint.new(self)
263
283
  end
@@ -266,8 +286,8 @@ module Dragonfly
266
286
  to_app.call(env)
267
287
  end
268
288
 
269
- def url(*args)
270
- app.url_for(self, *args) unless steps.empty?
289
+ def to_path
290
+ "/#{serialize}"
271
291
  end
272
292
 
273
293
  def to_fetched_job(uid)
@@ -277,8 +297,59 @@ module Dragonfly
277
297
  new_job
278
298
  end
279
299
 
280
- def to_path
281
- "/#{serialize}"
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)
282
353
  end
283
354
 
284
355
  def inspect
@@ -290,5 +361,11 @@ module Dragonfly
290
361
  attr_writer :steps
291
362
  attr_accessor :next_step_index
292
363
 
364
+ private
365
+
366
+ def last_step_of_type(type)
367
+ steps.select{|s| s.is_a?(type) }.last
368
+ end
369
+
293
370
  end
294
371
  end
@@ -1,14 +1,12 @@
1
1
  module Dragonfly
2
2
  class JobEndpoint
3
3
 
4
- include Endpoint
5
-
6
4
  def initialize(job)
7
5
  @job = job
8
6
  end
9
7
 
10
8
  def call(env={})
11
- response_for_job(job, env)
9
+ Response.new(job, env).to_response
12
10
  end
13
11
 
14
12
  attr_reader :job
@@ -3,6 +3,8 @@ require 'tempfile'
3
3
  module Dragonfly
4
4
  module RMagickUtils
5
5
 
6
+ include Loggable
7
+
6
8
  private
7
9
 
8
10
  # Requires the extended object to respond to 'use_filesystem'
@@ -0,0 +1,82 @@
1
+ require 'uri'
2
+
3
+ module Dragonfly
4
+ class Response
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
+ }
12
+
13
+ def initialize(job, env)
14
+ @job, @env = job, env
15
+ @app = @job.app
16
+ end
17
+
18
+ def to_response
19
+ if etag_matches?
20
+ # Not Modified
21
+ [304, cache_headers, []]
22
+ else
23
+ # Success
24
+ [200, success_headers.merge(cache_headers), job.result]
25
+ end
26
+ rescue DataStorage::DataNotFound => e
27
+ [404, {"Content-Type" => 'text/plain'}, [e.message]]
28
+ end
29
+
30
+ private
31
+
32
+ attr_reader :job, :env, :app
33
+
34
+ def request
35
+ @request ||= Rack::Request.new(env)
36
+ end
37
+
38
+ def cache_headers
39
+ {
40
+ "Cache-Control" => "public, max-age=#{app.cache_duration}",
41
+ "ETag" => %("#{job.unique_signature}")
42
+ }
43
+ end
44
+
45
+ def etag_matches?
46
+ if_none_match = env['HTTP_IF_NONE_MATCH']
47
+ if if_none_match
48
+ if_none_match.tr!('"','')
49
+ if_none_match.split(',').include?(job.unique_signature) || if_none_match == '*'
50
+ else
51
+ false
52
+ end
53
+ end
54
+
55
+ def success_headers
56
+ {
57
+ "Content-Type" => job.resolve_mime_type,
58
+ "Content-Length" => job.size.to_s
59
+ }.merge(content_disposition_header)
60
+ end
61
+
62
+ def content_disposition_header
63
+ parts = []
64
+ parts << content_disposition if content_disposition
65
+ parts << %(filename="#{URI.encode(filename)}") if filename
66
+ parts.any? ? {"Content-Disposition" => parts.join('; ')} : {}
67
+ end
68
+
69
+ def content_disposition
70
+ @content_disposition ||= evaluate(app.content_disposition)
71
+ end
72
+
73
+ def filename
74
+ @filename ||= evaluate(app.content_filename)
75
+ end
76
+
77
+ def evaluate(attribute)
78
+ attribute.respond_to?(:call) ? attribute.call(job, request) : attribute
79
+ end
80
+
81
+ end
82
+ end
@@ -1,8 +1,6 @@
1
1
  module Dragonfly
2
2
  class RoutedEndpoint
3
3
 
4
- include Endpoint
5
-
6
4
  class NoRoutingParams < RuntimeError; end
7
5
 
8
6
  def initialize(app, &block)
@@ -13,7 +11,7 @@ module Dragonfly
13
11
  def call(env)
14
12
  params = symbolize_keys_of Rack::Request.new(env).params
15
13
  job = @block.call(params.merge(routing_params(env)), @app)
16
- response_for_job(job, env)
14
+ Response.new(job, env).to_response
17
15
  rescue Job::NoSHAGiven => e
18
16
  [400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
19
17
  rescue Job::IncorrectSHA => e
@@ -1,7 +1,6 @@
1
1
  module Dragonfly
2
2
  class SimpleEndpoint
3
3
 
4
- include Endpoint
5
4
  include Loggable
6
5
 
7
6
  # Instance methods
@@ -20,7 +19,7 @@ module Dragonfly
20
19
  else
21
20
  job = Job.from_path(request.path_info, app)
22
21
  job.validate_sha!(request['s']) if app.protect_from_dos_attacks
23
- response_for_job(job, env)
22
+ Response.new(job, env).to_response
24
23
  end
25
24
  rescue Serializer::BadString, Job::InvalidArray => e
26
25
  log.warn(e.message)
@@ -47,7 +46,7 @@ module Dragonfly
47
46
  `-.~--' |=| '--~.-'
48
47
  _~-.~'" /|=|\\ "'~.-~_
49
48
  ( ./ |=| \\. )
50
- `~~`"` |=| `"'~~"
49
+ `~~`"` |=| `"'ME"
51
50
  |-|
52
51
  <->
53
52
  V
@@ -41,6 +41,7 @@ module Dragonfly
41
41
  # Instance Methods
42
42
 
43
43
  def initialize(obj, opts={})
44
+ opts ||= {} # in case it's nil
44
45
  initialize_from_object!(obj)
45
46
  validate_options!(opts)
46
47
  extract_attributes_from(opts)
@@ -57,6 +58,7 @@ module Dragonfly
57
58
  @tempfile = initialized_tempfile
58
59
  when :data
59
60
  @tempfile = Tempfile.new('dragonfly')
61
+ @tempfile.binmode
60
62
  @tempfile.write(initialized_data)
61
63
  when :file
62
64
  @tempfile = copy_to_tempfile(initialized_file.path)
@@ -89,8 +91,9 @@ module Dragonfly
89
91
  end
90
92
  end
91
93
 
92
- attr_reader :name, :format
94
+ attr_accessor :name, :format
93
95
  alias _format format
96
+ attr_writer :meta
94
97
 
95
98
  def meta
96
99
  @meta ||= {}
@@ -138,9 +141,9 @@ module Dragonfly
138
141
  end
139
142
 
140
143
  def extract_attributes_from(hash)
141
- self.name = hash.delete(:name) unless hash[:name].blank?
142
- self.meta = hash.delete(:meta) unless hash[:meta].blank?
143
- self.format = hash.delete(:format) unless hash[:format].blank?
144
+ self.name = hash.delete(:name) unless hash[:name].blank?
145
+ self.format = hash.delete(:format) unless hash[:format].blank?
146
+ self.meta.merge!(hash.delete(:meta)) unless hash[:meta].blank?
144
147
  end
145
148
 
146
149
  def inspect
@@ -160,8 +163,6 @@ module Dragonfly
160
163
 
161
164
  private
162
165
 
163
- attr_writer :name, :meta, :format
164
-
165
166
  def initialize_from_object!(obj)
166
167
  case obj
167
168
  when TempObject
@@ -615,6 +615,58 @@ describe Item do
615
615
  }.should raise_error(NoMethodError)
616
616
  end
617
617
  end
618
+
619
+ describe "setting things on the attachment" do
620
+
621
+ before(:each) do
622
+ @item = Item.new
623
+ end
624
+
625
+ describe "name" do
626
+ before(:each) do
627
+ @item.preview_image = "Hello"
628
+ @item.preview_image.name = 'hello.there'
629
+ end
630
+ it "should allow for setting the name" do
631
+ @item.preview_image.name.should == 'hello.there'
632
+ end
633
+ it "should update the magic attribute" do
634
+ @item.preview_image_name.should == 'hello.there'
635
+ end
636
+ it "should return the name" do
637
+ (@item.preview_image.name = 'no.silly').should == 'no.silly'
638
+ end
639
+ end
640
+
641
+ describe "meta" do
642
+ before(:each) do
643
+ @item.preview_image = "Hello all"
644
+ @item.preview_image.meta = {:slime => 'balls'}
645
+ end
646
+ it "should allow for setting the meta" do
647
+ @item.preview_image.meta.should == {:slime => 'balls'}
648
+ end
649
+ it "should allow for updating the meta" do
650
+ @item.preview_image.meta[:numb] = 'nuts'
651
+ @item.preview_image.meta.should == {:slime => 'balls', :numb => 'nuts'}
652
+ end
653
+ it "should return the meta" do
654
+ (@item.preview_image.meta = {:doogs => 'boogs'}).should == {:doogs => 'boogs'}
655
+ end
656
+ it "should save it correctly" do
657
+ @item.save!
658
+ item = Item.find(@item.id)
659
+ item.preview_image.meta.should include_hash(:slime => 'balls')
660
+ end
661
+ it "should store meta info about the model" do
662
+ @item.save!
663
+ meta = {:model_class => 'Item', :model_attachment => :preview_image, :slime => 'balls'}
664
+ @app.fetch(@item.preview_image_uid).meta.should == meta
665
+ end
666
+ end
667
+
668
+ end
669
+
618
670
  end
619
671
 
620
672
  describe "inheritance" do