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 +3 -0
- data/History.md +23 -0
- data/README.md +1 -1
- data/VERSION +1 -1
- data/dragonfly.gemspec +3 -3
- data/extra_docs/Configuration.md +6 -0
- data/extra_docs/Models.md +15 -0
- data/extra_docs/Rails2.md +1 -1
- data/extra_docs/Rails3.md +1 -1
- data/extra_docs/URLs.md +55 -2
- data/lib/dragonfly/active_model_extensions/attachment.rb +22 -9
- data/lib/dragonfly/analysis/r_magick_analyser.rb +0 -1
- data/lib/dragonfly/app.rb +12 -9
- data/lib/dragonfly/configurable.rb +4 -2
- data/lib/dragonfly/data_storage/file_data_store.rb +29 -20
- data/lib/dragonfly/data_storage/mongo_data_store.rb +7 -3
- data/lib/dragonfly/data_storage/s3data_store.rb +1 -1
- data/lib/dragonfly/job.rb +94 -17
- data/lib/dragonfly/job_endpoint.rb +1 -3
- data/lib/dragonfly/r_magick_utils.rb +2 -0
- data/lib/dragonfly/response.rb +82 -0
- data/lib/dragonfly/routed_endpoint.rb +1 -3
- data/lib/dragonfly/simple_endpoint.rb +2 -3
- data/lib/dragonfly/temp_object.rb +7 -6
- data/spec/dragonfly/active_model_extensions/model_spec.rb +52 -0
- data/spec/dragonfly/app_spec.rb +41 -8
- data/spec/dragonfly/configurable_spec.rb +2 -4
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +20 -7
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +25 -9
- data/spec/dragonfly/job_endpoint_spec.rb +95 -41
- data/spec/dragonfly/job_spec.rb +168 -0
- data/spec/dragonfly/temp_object_spec.rb +39 -25
- data/spec/simple_matchers.rb +2 -2
- metadata +4 -4
- data/lib/dragonfly/endpoint.rb +0 -43
data/.gitignore
CHANGED
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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.7.
|
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.
|
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-
|
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",
|
data/extra_docs/Configuration.md
CHANGED
@@ -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
data/extra_docs/Rails3.md
CHANGED
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
|
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')
|
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
|
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
|
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{|
|
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?(
|
160
|
+
magic_attributes.include?(property.to_sym)
|
148
161
|
end
|
149
162
|
|
150
163
|
def magic_attribute_for(property)
|
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
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
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].
|
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
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
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
|
-
|
31
|
+
|
32
|
+
relative(path)
|
29
33
|
end
|
30
34
|
|
31
35
|
def retrieve(relative_path)
|
32
|
-
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 =
|
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(
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
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 =
|
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
|