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.

data/.gitignore CHANGED
@@ -10,3 +10,6 @@ fixtures/*/tmp_app
10
10
  .ginger
11
11
  .bundle
12
12
  spec/spec.log
13
+ Gemfile.lock
14
+ .s3_spec.yml
15
+
data/History.md CHANGED
@@ -1,3 +1,26 @@
1
+ 0.7.6 (2010-09-12)
2
+ ==================
3
+ Features
4
+ --------
5
+ - Added methods for querying job steps, and Job#uid, Job#uid_basename, etc.
6
+ - Added Job#b64_data
7
+ - Added configurable url_suffix
8
+ - Added configurable content_disposition and content_filename
9
+ - Can pass extra GET params to url_for
10
+ - Can manually set uid on FileDataStore and S3DataStore
11
+ (not yet documented because attachments have no way to pass it on yet)
12
+ - Model attachments store meta info about themselves
13
+
14
+ Changes
15
+ -------
16
+ - Configurable module doesn't implicitly call 'call' if attribute set as proc
17
+ - Refactored Endpoint module -> Response object
18
+
19
+ Fixes
20
+ -----
21
+ - Ruby 1.9.2-p0 was raising encoding errors due to Tempfiles not being in binmode
22
+
23
+
1
24
  0.7.5 (2010-09-01)
2
25
  ==================
3
26
  Changes
data/README.md CHANGED
@@ -11,7 +11,7 @@ For the lazy Rails user...
11
11
 
12
12
  gem 'rmagick', :require => 'RMagick'
13
13
  gem 'rack-cache', :require => 'rack/cache'
14
- gem 'dragonfly', '~>0.7.4'
14
+ gem 'dragonfly', '~>0.7.5'
15
15
 
16
16
  **Initializer** (e.g. config/initializers/dragonfly.rb):
17
17
 
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.7.5
1
+ 0.7.6
data/dragonfly.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{dragonfly}
8
- s.version = "0.7.5"
8
+ s.version = "0.7.6"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Mark Evans"]
12
- s.date = %q{2010-09-01}
12
+ s.date = %q{2010-09-12}
13
13
  s.email = %q{mark@new-bamboo.co.uk}
14
14
  s.extra_rdoc_files = [
15
15
  "LICENSE",
@@ -89,7 +89,6 @@ Gem::Specification.new do |s|
89
89
  "lib/dragonfly/data_storage/s3data_store.rb",
90
90
  "lib/dragonfly/encoder.rb",
91
91
  "lib/dragonfly/encoding/r_magick_encoder.rb",
92
- "lib/dragonfly/endpoint.rb",
93
92
  "lib/dragonfly/function_manager.rb",
94
93
  "lib/dragonfly/generation/r_magick_generator.rb",
95
94
  "lib/dragonfly/generator.rb",
@@ -103,6 +102,7 @@ Gem::Specification.new do |s|
103
102
  "lib/dragonfly/processor.rb",
104
103
  "lib/dragonfly/r_magick_utils.rb",
105
104
  "lib/dragonfly/rails/images.rb",
105
+ "lib/dragonfly/response.rb",
106
106
  "lib/dragonfly/routed_endpoint.rb",
107
107
  "lib/dragonfly/serializer.rb",
108
108
  "lib/dragonfly/simple_cache.rb",
@@ -29,6 +29,12 @@ Here is an example of an app with all attributes configured:
29
29
 
30
30
  c.url_path_prefix = '/images' # defaults to nil
31
31
  c.url_host = 'http://some.domain.com:4000' # defaults to nil
32
+ c.url_suffix = '.jpg' # defaults to nil - has no effect other than change the url
33
+
34
+ c.content_filename = proc{|job, request| # defaults to the original name, with modified ext if encoded
35
+ "file.#{job.ext}"
36
+ }
37
+ c.content_disposition = :attachment # defaults to nil (use the browser default)
32
38
 
33
39
  c.protect_from_dos_attacks = true # defaults to false - adds a SHA parameter on the end of urls
34
40
  c.secret = 'This is my secret yeh!!' # should set this if concerned about DOS attacks
data/extra_docs/Models.md CHANGED
@@ -174,6 +174,21 @@ If the object assigned is a file, or responds to `original_filename` (as is the
174
174
  @album.cover_image.name # => 'my_image.png'
175
175
  @album.cover_image.ext # => 'png'
176
176
 
177
+ Meta data
178
+ ---------
179
+ You can store metadata along with the content data of your attachment:
180
+
181
+ @album.cover_image = File.new('path/to/my_image.png')
182
+ @album.cover_image.meta = {:taken => Date.yesterday}
183
+ @album.save!
184
+
185
+ @album.cover_image.meta # => {:model_class=>"Album",
186
+ # :model_attachment=>:cover_image,
187
+ # :taken=>Sat, 11 Sep 2010}
188
+
189
+ As you can see, a couple of things are added by the model. You can also access this directly on the {Dragonfly::Job Job} object.
190
+
191
+ app.fetch(@album.cover_image_uid).meta # => {:model_class=>"Album", ...}
177
192
 
178
193
  'Magic' Attributes
179
194
  ------------------
data/extra_docs/Rails2.md CHANGED
@@ -34,7 +34,7 @@ Gems
34
34
  ----
35
35
  environment.rb
36
36
 
37
- config.gem 'dragonfly', '~>0.7.4'
37
+ config.gem 'dragonfly', '~>0.7.5'
38
38
  config.gem 'rmagick', :lib => 'RMagick'
39
39
  config.gem 'rack-cache', :lib => 'rack/cache'
40
40
 
data/extra_docs/Rails3.md CHANGED
@@ -33,7 +33,7 @@ application.rb:
33
33
  Gemfile
34
34
  -------
35
35
 
36
- gem 'dragonfly', '~>0.7.4'
36
+ gem 'dragonfly', '~>0.7.5'
37
37
  gem 'rmagick', :require => 'RMagick'
38
38
  gem 'rack-cache', :require => 'rack/cache'
39
39
 
data/extra_docs/URLs.md CHANGED
@@ -17,13 +17,13 @@ to the urls:
17
17
 
18
18
  (or done in a configuration block).
19
19
 
20
- app.fetch('my_uid').url # "/media/BAhbBlsH..."
20
+ app.fetch('my_uid').url # "/media/BAhbBlsH..."
21
21
 
22
22
  This is done for you when using {file:Configuration Rails defaults}.
23
23
 
24
24
  You can override it using
25
25
 
26
- app.fetch('my_uid').url(:path_prefix => '/images') # "/images/BAhbBlsH..."
26
+ app.fetch('my_uid').url(:path_prefix => '/images') # "/images/BAhbBlsH..."
27
27
 
28
28
  Host
29
29
  ----
@@ -34,6 +34,59 @@ You can also set a host for the urls
34
34
 
35
35
  app.fetch('my_uid').url(:host => 'http://localhost:80') # "http://localhost:80/BAh..."
36
36
 
37
+ Suffix
38
+ ------
39
+ You can set a suffix for the urls (for example if some other component behaves badly with urls that have no file extension).
40
+
41
+ Note that this has no effect on the response.
42
+
43
+ app.url_suffix = '.jpg'
44
+ app.fetch('some/uid').url # "...b21lL3VpZA.jpg"
45
+
46
+ You can also pass it a block, that yields the {Dragonfly::Job Job}, for example:
47
+
48
+ app.url_suffix = proc{|job|
49
+ "/#{job.uid_basename}#{job.encoded_extname || job.uid_extname}"
50
+ }
51
+
52
+ app.fetch('2007/painting.pdf').url # "...eS5ib2R5/painting.pdf"
53
+ app.fetch('2007/painting.pdf').encode(:png).url # "...gZlOgbmc/painting.png"
54
+
55
+ And you can override it:
56
+
57
+ app.fetch('some/uid').url(:suffix => '/yellowbelly') # "...b21lL3VpZA/yellowbelly"
58
+
59
+ Content-Disposition and downloaded filename
60
+ -------------------------------------------
61
+ You can manually set the content-disposition of the response:
62
+
63
+ app.content_disposition = :attachment # should be :inline or :attachment (or :hidden)
64
+
65
+ `:attachment` tells the browser to download it, `:inline` tells it to display in-browser if possible.
66
+
67
+ You can also use a block:
68
+
69
+ app.content_disposition = proc{|job, request|
70
+ if job.format == :jpg || request['d'] == 'inline' # request is a Rack::Request object
71
+ :inline
72
+ else
73
+ :attachment
74
+ end
75
+ }
76
+
77
+ To specify the filename the browser uses for 'Save As' dialogues:
78
+
79
+ app.content_filename = proc{|job, request|
80
+ "#{job.basename}_#{job.process_steps.first.name}.#{job.encoded_format || job.ext}"
81
+ }
82
+
83
+ This will for example give the following filenames for the following jobs:
84
+
85
+ app.fetch('some/tree.png').process(:greyscale) # -> 'tree_greyscale.png'
86
+ app.fetch('some/tree.png').process(:greyscale).gif # -> 'tree_greyscale.gif'
87
+
88
+ By default the original filename is used, with a modified extension if it's been encoded.
89
+
37
90
  Routed Endpoints
38
91
  ----------------
39
92
  You can also use a number of Rack-based routers and create Dragonfly endpoints.
@@ -9,6 +9,7 @@ module Dragonfly
9
9
  def_delegators :job,
10
10
  :data, :to_file, :file, :tempfile, :path,
11
11
  :process, :encode, :analyse,
12
+ :meta, :meta=,
12
13
  :url
13
14
 
14
15
  def initialize(app, parent_model, attribute_name)
@@ -44,7 +45,12 @@ module Dragonfly
44
45
  sync_with_parent!
45
46
  destroy_previous!
46
47
  if job && !uid
47
- set_uid_and_parent_uid app.store(job.result)
48
+ set_uid_and_parent_uid job.store(
49
+ :meta => {
50
+ :model_class => parent_model.class.name,
51
+ :model_attachment => attribute_name
52
+ }
53
+ )
48
54
  self.job = job.to_fetched_job(uid)
49
55
  end
50
56
  end
@@ -63,6 +69,12 @@ module Dragonfly
63
69
  end
64
70
  end
65
71
 
72
+ def name=(name)
73
+ job.name = name
74
+ set_magic_attribute(:name, name) if has_magic_attribute_for?(:name)
75
+ name
76
+ end
77
+
66
78
  def process!(*args)
67
79
  assign(process(*args))
68
80
  self
@@ -127,24 +139,25 @@ module Dragonfly
127
139
  end
128
140
 
129
141
  def magic_attributes
130
- parent_model.public_methods.select { |name|
142
+ @magic_attributes ||= parent_model.public_methods.select { |name|
131
143
  name.to_s =~ /^#{attribute_name}_(.+)$/ && allowed_magic_attributes.include?($1.to_sym)
132
- }
144
+ }.map{|name| name.to_s.sub("#{attribute_name}_", '').to_sym }
145
+ end
146
+
147
+ def set_magic_attribute(property, value)
148
+ parent_model.send("#{attribute_name}_#{property}=", value)
133
149
  end
134
150
 
135
151
  def set_magic_attributes
136
- magic_attributes.each do |attribute|
137
- method = attribute.to_s.sub("#{attribute_name}_", '')
138
- parent_model.send("#{attribute}=", job.send(method))
139
- end
152
+ magic_attributes.each{|property| set_magic_attribute(property, job.send(property)) }
140
153
  end
141
154
 
142
155
  def reset_magic_attributes
143
- magic_attributes.each{|attribute| parent_model.send("#{attribute}=", nil) }
156
+ magic_attributes.each{|property| set_magic_attribute(property, nil) }
144
157
  end
145
158
 
146
159
  def has_magic_attribute_for?(property)
147
- magic_attributes.include?("#{attribute_name}_#{property}".to_method_name)
160
+ magic_attributes.include?(property.to_sym)
148
161
  end
149
162
 
150
163
  def magic_attribute_for(property)
@@ -4,7 +4,6 @@ module Dragonfly
4
4
  module Analysis
5
5
  class RMagickAnalyser
6
6
 
7
- include Loggable
8
7
  include RMagickUtils
9
8
  include Configurable
10
9
 
data/lib/dragonfly/app.rb CHANGED
@@ -45,10 +45,13 @@ module Dragonfly
45
45
  configurable_attr :fallback_mime_type, 'application/octet-stream'
46
46
  configurable_attr :url_path_prefix
47
47
  configurable_attr :url_host
48
+ configurable_attr :url_suffix
48
49
  configurable_attr :protect_from_dos_attacks, false
49
50
  configurable_attr :secret, 'secret yo'
50
51
  configurable_attr :log do Logger.new('/var/tmp/dragonfly.log') end
51
52
  configurable_attr :infer_mime_type_from_file_ext, true
53
+ configurable_attr :content_disposition
54
+ configurable_attr :content_filename, Response::DEFAULT_FILENAME
52
55
 
53
56
  attr_reader :analyser
54
57
  attr_reader :processor
@@ -124,11 +127,15 @@ module Dragonfly
124
127
 
125
128
  def url_for(job, *args)
126
129
  if (args.length == 1 && args.first.kind_of?(Hash)) || args.empty?
127
- opts = args.first || {}
128
- host = opts[:host] || url_host
129
- path_prefix = opts[:path_prefix] || url_path_prefix
130
- path = "#{host}#{path_prefix}#{job.to_path}"
131
- path << "?#{dos_protection_query_string(job)}" if protect_from_dos_attacks
130
+ opts = args.first ? args.first.dup : {}
131
+ host = opts.delete(:host) || url_host
132
+ suffix = opts.delete(:suffix) || url_suffix
133
+ suffix = suffix.call(job) if suffix.respond_to?(:call)
134
+ path_prefix = opts.delete(:path_prefix) || url_path_prefix
135
+ path = "#{host}#{path_prefix}#{job.to_path}#{suffix}"
136
+ query = opts
137
+ query.merge!(server.required_params_for(job)) if protect_from_dos_attacks
138
+ path << "?#{Rack::Utils.build_query(query)}" if query.any?
132
139
  path
133
140
  else
134
141
  # Deprecation stuff - will be removed!!!
@@ -169,9 +176,5 @@ module Dragonfly
169
176
  '.' + format.to_s.downcase.sub(/^.*\./,'')
170
177
  end
171
178
 
172
- def dos_protection_query_string(job)
173
- server.required_params_for(job).map{|k,v| "#{k}=#{v}" }.join('&')
174
- end
175
-
176
179
  end
177
180
  end
@@ -25,6 +25,8 @@ module Dragonfly
25
25
  end
26
26
  end
27
27
 
28
+ class DeferredBlock < Proc; end
29
+
28
30
  module InstanceMethods
29
31
 
30
32
  def configure(&block)
@@ -62,11 +64,11 @@ module Dragonfly
62
64
  private
63
65
 
64
66
  def configurable_attr attribute, default=nil, &blk
65
- default_configuration[attribute] = blk || default
67
+ default_configuration[attribute] = blk ? DeferredBlock.new(&blk) : default
66
68
 
67
69
  # Define the reader
68
70
  define_method(attribute) do
69
- if configuration_hash[attribute].respond_to? :call
71
+ if configuration_hash[attribute].is_a?(DeferredBlock)
70
72
  configuration_hash[attribute] = configuration_hash[attribute].call
71
73
  end
72
74
  configuration_hash[attribute]
@@ -10,13 +10,17 @@ module Dragonfly
10
10
  configurable_attr :root_path, '/var/tmp/dragonfly'
11
11
 
12
12
  def store(temp_object, opts={})
13
- filename = temp_object.name || 'file'
13
+ relative_path = if opts[:path]
14
+ opts[:path]
15
+ else
16
+ filename = temp_object.name || 'file'
17
+ relative_path = relative_path_for(filename)
18
+ end
14
19
 
15
- relative_path = relative_path_for(filename)
16
20
  begin
17
- while File.exist?(path = absolute_path(relative_path))
18
- filename = disambiguate(filename)
19
- relative_path = relative_path_for(filename)
21
+ path = absolute(relative_path)
22
+ until !File.exist?(path)
23
+ path = disambiguate(path)
20
24
  end
21
25
  prepare_path(path)
22
26
  temp_object.to_file(path).close
@@ -24,12 +28,12 @@ module Dragonfly
24
28
  rescue Errno::EACCES => e
25
29
  raise UnableToStore, e.message
26
30
  end
27
-
28
- relative_path
31
+
32
+ relative(path)
29
33
  end
30
34
 
31
35
  def retrieve(relative_path)
32
- path = absolute_path(relative_path)
36
+ path = absolute(relative_path)
33
37
  [
34
38
  File.new(path),
35
39
  retrieve_extra_data(path)
@@ -39,7 +43,7 @@ module Dragonfly
39
43
  end
40
44
 
41
45
  def destroy(relative_path)
42
- path = absolute_path(relative_path)
46
+ path = absolute(relative_path)
43
47
  FileUtils.rm path
44
48
  FileUtils.rm extra_data_path(path)
45
49
  purge_empty_directories(relative_path)
@@ -47,18 +51,23 @@ module Dragonfly
47
51
  raise DataNotFound, e.message
48
52
  end
49
53
 
50
- def disambiguate(filename)
51
- basename = File.basename(filename, '.*')
52
- extname = File.extname(filename)
53
- "#{basename}_#{Time.now.usec.to_s(32)}#{extname}"
54
+ def disambiguate(path)
55
+ dirname = File.dirname(path)
56
+ basename = File.basename(path, '.*')
57
+ extname = File.extname(path)
58
+ "#{dirname}/#{basename}_#{Time.now.usec.to_s(32)}#{extname}"
54
59
  end
55
60
 
56
61
  private
57
62
 
58
- def absolute_path(relative_path)
63
+ def absolute(relative_path)
59
64
  File.join(root_path, relative_path)
60
65
  end
61
66
 
67
+ def relative(absolute_path)
68
+ absolute_path[/^#{root_path}\/(.*)$/, 1]
69
+ end
70
+
62
71
  def directory_empty?(path)
63
72
  Dir.entries(path) == ['.','..']
64
73
  end
@@ -70,31 +79,31 @@ module Dragonfly
70
79
  def relative_path_for(filename)
71
80
  "#{Time.now.strftime '%Y/%m/%d'}/#{filename.gsub(/[^\w.]+/,'_')}"
72
81
  end
73
-
82
+
74
83
  def store_extra_data(data_path, temp_object)
75
84
  File.open(extra_data_path(data_path), 'w') do |f|
76
85
  f.write Marshal.dump(temp_object.attributes)
77
86
  end
78
87
  end
79
-
88
+
80
89
  def retrieve_extra_data(data_path)
81
90
  path = extra_data_path(data_path)
82
91
  File.exist?(path) ? Marshal.load(File.read(path)) : {}
83
92
  end
84
-
93
+
85
94
  def prepare_path(path)
86
95
  dir = File.dirname(path)
87
96
  FileUtils.mkdir_p(dir) unless File.exist?(dir)
88
97
  end
89
-
98
+
90
99
  def purge_empty_directories(path)
91
100
  containing_directory = Pathname.new(path).dirname
92
101
  containing_directory.ascend do |relative_dir|
93
- dir = absolute_path(relative_dir)
102
+ dir = absolute(relative_dir)
94
103
  FileUtils.rmdir dir if directory_empty?(dir)
95
104
  end
96
105
  end
97
-
106
+
98
107
  end
99
108
 
100
109
  end