dragonfly 0.1.6 → 0.2.1
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/.yardopts +4 -0
- data/{README.markdown → README.md} +12 -24
- data/Rakefile +6 -6
- data/VERSION +1 -1
- data/config.rb +1 -3
- data/docs.watchr +1 -1
- data/dragonfly-rails.gemspec +2 -5
- data/dragonfly.gemspec +32 -12
- data/extra_docs/ActiveRecord.md +195 -0
- data/extra_docs/Analysers.md +63 -0
- data/extra_docs/DataStorage.md +33 -0
- data/extra_docs/Encoding.md +58 -0
- data/extra_docs/GettingStarted.md +114 -0
- data/extra_docs/Processing.md +58 -0
- data/extra_docs/Shortcuts.md +118 -0
- data/extra_docs/UsingWithRails.md +104 -0
- data/features/{dragonfly.feature → images.feature} +14 -4
- data/features/no_processing.feature +20 -0
- data/features/steps/dragonfly_steps.rb +29 -8
- data/features/support/env.rb +20 -8
- data/generators/dragonfly_app/USAGE +0 -1
- data/generators/dragonfly_app/dragonfly_app_generator.rb +1 -13
- data/generators/dragonfly_app/templates/metal_file.erb +1 -1
- data/lib/dragonfly/active_record_extensions.rb +1 -0
- data/lib/dragonfly/active_record_extensions/attachment.rb +52 -6
- data/lib/dragonfly/active_record_extensions/validations.rb +26 -6
- data/lib/dragonfly/analysis/base.rb +6 -3
- data/lib/dragonfly/analysis/r_magick_analyser.rb +0 -6
- data/lib/dragonfly/app.rb +53 -35
- data/lib/dragonfly/configurable.rb +1 -1
- data/lib/dragonfly/data_storage/file_data_store.rb +8 -8
- data/lib/dragonfly/delegatable.rb +14 -0
- data/lib/dragonfly/delegator.rb +50 -0
- data/lib/dragonfly/encoding/base.rb +7 -7
- data/lib/dragonfly/encoding/r_magick_encoder.rb +3 -0
- data/lib/dragonfly/encoding/transparent_encoder.rb +1 -1
- data/lib/dragonfly/extended_temp_object.rb +13 -7
- data/lib/dragonfly/parameters.rb +17 -11
- data/lib/dragonfly/processing/base.rb +9 -0
- data/lib/dragonfly/processing/r_magick_processor.rb +15 -1
- data/lib/dragonfly/r_magick_configuration.rb +12 -8
- data/lib/dragonfly/rails/images.rb +1 -1
- data/lib/dragonfly/temp_object.rb +14 -2
- data/lib/dragonfly/url_handler.rb +5 -6
- data/samples/sample.docx +0 -0
- data/spec/dragonfly/active_record_extensions/model_spec.rb +175 -84
- data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +0 -8
- data/spec/dragonfly/app_spec.rb +3 -3
- data/spec/dragonfly/configurable_spec.rb +1 -1
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +55 -40
- data/spec/dragonfly/delegatable_spec.rb +32 -0
- data/spec/dragonfly/delegator_spec.rb +133 -0
- data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +28 -0
- data/spec/dragonfly/extended_temp_object_spec.rb +5 -5
- data/spec/dragonfly/parameters_spec.rb +22 -32
- data/spec/dragonfly/processing/rmagick_processor_spec.rb +1 -2
- data/spec/dragonfly/temp_object_spec.rb +51 -0
- data/spec/dragonfly/url_handler_spec.rb +10 -15
- data/yard/handlers/configurable_attr_handler.rb +38 -0
- data/yard/setup.rb +9 -0
- data/yard/templates/default/fulldoc/html/css/common.css +27 -0
- data/yard/templates/default/module/html/configuration_summary.erb +31 -0
- data/yard/templates/default/module/setup.rb +17 -0
- metadata +31 -12
- data/features/support/image_helpers.rb +0 -9
- data/generators/dragonfly_app/templates/custom_processing.erb +0 -13
- data/lib/dragonfly/analysis/analyser.rb +0 -45
- data/lib/dragonfly/processing/processor.rb +0 -14
- data/spec/dragonfly/analysis/analyser_spec.rb +0 -85
@@ -1,5 +1,4 @@
|
|
1
1
|
require 'rmagick'
|
2
|
-
require 'mime/types'
|
3
2
|
|
4
3
|
module Dragonfly
|
5
4
|
module Analysis
|
@@ -14,11 +13,6 @@ module Dragonfly
|
|
14
13
|
rmagick_image(image).rows
|
15
14
|
end
|
16
15
|
|
17
|
-
def mime_type(image)
|
18
|
-
MIME::Types.type_for(rmagick_image(image).format).first.to_s
|
19
|
-
rescue Magick::ImageMagickError
|
20
|
-
end
|
21
|
-
|
22
16
|
def depth(image)
|
23
17
|
rmagick_image(image).depth
|
24
18
|
end
|
data/lib/dragonfly/app.rb
CHANGED
@@ -19,10 +19,12 @@ module Dragonfly
|
|
19
19
|
# @example Example configuration options:
|
20
20
|
#
|
21
21
|
# Dragonfly::App[:images].configure do |c|
|
22
|
-
# c.datastore = MyEC2DataStore.new
|
23
|
-
# c.
|
24
|
-
# c.
|
25
|
-
# c.
|
22
|
+
# c.datastore = MyEC2DataStore.new # See DataStorage::Base for how to create a custom data store
|
23
|
+
# c.register_analyser(Analysis::RMagickAnalyser) # See Analysis::Base for how to create a custom analyser
|
24
|
+
# c.register_processor(Processing::RMagickProcessor) # See Processing::Base for how to create a custom analyser
|
25
|
+
# c.register_encoder(Encoding::RMagickEncoder) # See Encoding::Base for how to create a custom encoder
|
26
|
+
# c.log = Logger.new('/tmp/my.log')
|
27
|
+
# c.cache_duration = 3000 # seconds
|
26
28
|
# end
|
27
29
|
#
|
28
30
|
# @example Configuration including nested items
|
@@ -32,12 +34,6 @@ module Dragonfly
|
|
32
34
|
# c.datastore.configure do |d| # configuration depends on which data store you use
|
33
35
|
# # ...
|
34
36
|
# end
|
35
|
-
# c.analyser.configure do |a| # see Analysis::Analyser
|
36
|
-
# # ...
|
37
|
-
# end
|
38
|
-
# c.processor.configure do |p| # see Processing::Processor
|
39
|
-
# # ...
|
40
|
-
# end
|
41
37
|
# c.parameters.configure do |p| # see Parameters (class methods)
|
42
38
|
# # ...
|
43
39
|
# end
|
@@ -70,40 +66,41 @@ module Dragonfly
|
|
70
66
|
def apps
|
71
67
|
@apps ||= {}
|
72
68
|
end
|
73
|
-
|
69
|
+
|
74
70
|
end
|
75
71
|
|
76
72
|
def initialize
|
77
|
-
@
|
78
|
-
@processor = Processing::Processor.new
|
73
|
+
@analysers, @processors, @encoders = Delegator.new, Delegator.new, Delegator.new
|
79
74
|
@parameters_class = Class.new(Parameters)
|
80
75
|
@url_handler = UrlHandler.new(@parameters_class)
|
81
76
|
initialize_temp_object_class
|
82
77
|
end
|
83
78
|
|
84
|
-
# @see Analysis::
|
85
|
-
attr_reader :
|
86
|
-
# @see Processing::
|
87
|
-
attr_reader :
|
88
|
-
# @see Encoding::
|
89
|
-
attr_reader :
|
79
|
+
# @see Analysis::AnalyserList
|
80
|
+
attr_reader :analysers
|
81
|
+
# @see Processing::ProcessorList
|
82
|
+
attr_reader :processors
|
83
|
+
# @see Encoding::EncoderList
|
84
|
+
attr_reader :encoders
|
90
85
|
# @see UrlHandler
|
91
86
|
attr_reader :url_handler
|
92
87
|
# @see Parameters
|
93
88
|
attr_reader :parameters_class
|
89
|
+
# @see TempObject, and ExtendedTempObject
|
90
|
+
attr_reader :temp_object_class
|
94
91
|
|
95
92
|
alias parameters parameters_class
|
96
93
|
|
97
94
|
extend Forwardable
|
98
95
|
def_delegator :url_handler, :url_for
|
96
|
+
def_delegator :datastore, :destroy
|
99
97
|
|
100
98
|
include Configurable
|
101
99
|
|
102
100
|
configurable_attr :datastore do DataStorage::FileDataStore.new end
|
103
|
-
configurable_attr :encoder do Encoding::TransparentEncoder.new end
|
104
101
|
configurable_attr :log do Logger.new('/var/tmp/dragonfly.log') end
|
105
|
-
configurable_attr :cache_duration, 3600*24*365 #
|
106
|
-
configurable_attr :fallback_mime_type, 'application/octet-stream'
|
102
|
+
configurable_attr :cache_duration, 3600*24*365 # (1 year)
|
103
|
+
configurable_attr :fallback_mime_type, 'application/octet-stream'
|
107
104
|
|
108
105
|
# The call method required by Rack to run.
|
109
106
|
#
|
@@ -127,16 +124,17 @@ module Dragonfly
|
|
127
124
|
"Cache-Control" => "public, max-age=#{cache_duration}"
|
128
125
|
}, temp_object]
|
129
126
|
rescue UrlHandler::IncorrectSHA, UrlHandler::SHANotGiven => e
|
127
|
+
warn_with_info(e.message, env)
|
130
128
|
[400, {"Content-Type" => "text/plain"}, [e.message]]
|
131
129
|
rescue UrlHandler::UnknownUrl, DataStorage::DataNotFound => e
|
132
130
|
[404, {"Content-Type" => 'text/plain'}, [e.message]]
|
133
131
|
end
|
134
132
|
|
135
|
-
#
|
136
|
-
# @param [String, File, Tempfile, TempObject]
|
137
|
-
# @return [
|
138
|
-
def
|
139
|
-
|
133
|
+
# Create a temp_object from the object passed in
|
134
|
+
# @param [String, File, Tempfile, TempObject] initialization_object the object holding the data
|
135
|
+
# @return [ExtendedTempObject] a temp_object holding the data
|
136
|
+
def create_object(initialization_object)
|
137
|
+
temp_object_class.new(initialization_object)
|
140
138
|
end
|
141
139
|
|
142
140
|
# Fetch an object from the database and optionally transform
|
@@ -156,22 +154,42 @@ module Dragonfly
|
|
156
154
|
temp_object.transform(*args)
|
157
155
|
end
|
158
156
|
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
157
|
+
def generate(*args)
|
158
|
+
create_object(processors.generate(*args))
|
159
|
+
end
|
160
|
+
|
161
|
+
# Store an object, using the configured datastore
|
162
|
+
# @param [String, File, Tempfile, TempObject] object the object holding the data
|
163
|
+
# @return [String] the uid assigned to it
|
164
|
+
def store(object)
|
165
|
+
datastore.store(create_object(object))
|
164
166
|
end
|
165
167
|
|
168
|
+
def register_analyser(*args, &block)
|
169
|
+
analysers.register(*args, &block)
|
170
|
+
end
|
171
|
+
configuration_method :register_analyser
|
172
|
+
|
173
|
+
def register_processor(*args, &block)
|
174
|
+
processors.register(*args, &block)
|
175
|
+
end
|
176
|
+
configuration_method :register_processor
|
177
|
+
|
178
|
+
def register_encoder(*args, &block)
|
179
|
+
encoders.register(*args, &block)
|
180
|
+
end
|
181
|
+
configuration_method :register_encoder
|
182
|
+
|
166
183
|
private
|
167
184
|
|
168
|
-
# @private
|
169
|
-
attr_reader :temp_object_class
|
170
|
-
|
171
185
|
def initialize_temp_object_class
|
172
186
|
@temp_object_class = Class.new(ExtendedTempObject)
|
173
187
|
@temp_object_class.app = self
|
174
188
|
end
|
175
189
|
|
190
|
+
def warn_with_info(message, env)
|
191
|
+
log.warn "Got error: #{message}\nPath was #{env['PATH_INFO'].inspect} and query was #{env['QUERY_STRING'].inspect}"
|
192
|
+
end
|
193
|
+
|
176
194
|
end
|
177
195
|
end
|
@@ -93,7 +93,7 @@ module Dragonfly
|
|
93
93
|
if owner.has_configuration_method?(method_name)
|
94
94
|
owner.send(method_name, *args, &block)
|
95
95
|
elsif nested_configurable?(method_name, *args)
|
96
|
-
owner.send(method_name, *args)
|
96
|
+
owner.send(method_name, *args)
|
97
97
|
else
|
98
98
|
raise BadConfigAttribute, "You tried to configure using '#{method_name.inspect}', but the valid config attributes are #{owner.configuration_methods.map{|a| %('#{a.inspect}') }.sort.join(', ')}"
|
99
99
|
end
|
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'pathname'
|
2
|
+
|
1
3
|
module Dragonfly
|
2
4
|
module DataStorage
|
3
5
|
|
@@ -12,7 +14,7 @@ module Dragonfly
|
|
12
14
|
suffix = if temp_object.name.blank?
|
13
15
|
'file'
|
14
16
|
else
|
15
|
-
temp_object.name
|
17
|
+
temp_object.name.sub(/\.[^.]*?$/, '')
|
16
18
|
end
|
17
19
|
relative_path = relative_storage_path(suffix)
|
18
20
|
|
@@ -20,20 +22,18 @@ module Dragonfly
|
|
20
22
|
while File.exist?(storage_path = absolute_storage_path(relative_path))
|
21
23
|
relative_path = increment_path(relative_path)
|
22
24
|
end
|
23
|
-
|
25
|
+
storage_dir = File.dirname(storage_path)
|
26
|
+
FileUtils.mkdir_p(storage_dir) unless File.exist?(storage_dir)
|
24
27
|
FileUtils.cp temp_object.path, storage_path
|
25
28
|
rescue Errno::EACCES => e
|
26
29
|
raise UnableToStore, e.message
|
27
30
|
end
|
28
31
|
|
29
|
-
relative_path
|
32
|
+
relative_path
|
30
33
|
end
|
31
34
|
|
32
|
-
def retrieve(
|
33
|
-
|
34
|
-
entries = Dir[pattern]
|
35
|
-
raise DataNotFound, "Couldn't find any files matching #{pattern}" if entries.empty?
|
36
|
-
File.new(entries.first)
|
35
|
+
def retrieve(relative_path)
|
36
|
+
File.new(absolute_storage_path(relative_path))
|
37
37
|
rescue Errno::ENOENT => e
|
38
38
|
raise DataNotFound, e.message
|
39
39
|
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module Delegatable
|
3
|
+
|
4
|
+
def delegatable_methods
|
5
|
+
if @delegatable_methods
|
6
|
+
@delegatable_methods
|
7
|
+
else
|
8
|
+
ancestors = self.class.ancestors
|
9
|
+
@delegatable_methods = ancestors[0...ancestors.index(Delegatable)].map{|i| i.instance_methods(false) }.flatten
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
class Delegator
|
3
|
+
|
4
|
+
# This gets raised if no delegated objects are able to handle
|
5
|
+
# the method call, even though they respond to that method.
|
6
|
+
class UnableToHandle < StandardError; end
|
7
|
+
|
8
|
+
def register(klass, *args, &block)
|
9
|
+
object = klass.new(*args)
|
10
|
+
object.configure(&block) if block
|
11
|
+
registered_objects << object
|
12
|
+
end
|
13
|
+
|
14
|
+
def unregister(klass)
|
15
|
+
registered_objects.delete_if{|obj| obj.is_a?(klass) }
|
16
|
+
end
|
17
|
+
|
18
|
+
def unregister_all
|
19
|
+
self.registered_objects = []
|
20
|
+
end
|
21
|
+
|
22
|
+
def registered_objects
|
23
|
+
@registered_objects ||= []
|
24
|
+
end
|
25
|
+
|
26
|
+
def callable_methods
|
27
|
+
registered_objects.map{|a| a.delegatable_methods }.flatten.uniq
|
28
|
+
end
|
29
|
+
|
30
|
+
def has_callable_method?(method)
|
31
|
+
callable_methods.include?(method.to_s)
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
attr_writer :registered_objects
|
37
|
+
|
38
|
+
def method_missing(meth, *args)
|
39
|
+
registered_objects.reverse.each do |object|
|
40
|
+
catch :unable_to_handle do
|
41
|
+
return object.send(meth, *args) if object.respond_to?(meth)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
raise UnableToHandle, "None of the registered objects for #{self} were able to deal with the method call " +
|
45
|
+
"#{meth}(#{args.map{|a| a.inspect[0..100]}.join(',')}), even though the method is implemented" if self.has_callable_method?(meth)
|
46
|
+
super
|
47
|
+
end
|
48
|
+
|
49
|
+
end
|
50
|
+
end
|
@@ -1,13 +1,13 @@
|
|
1
1
|
module Dragonfly
|
2
|
-
module Encoding
|
3
|
-
|
2
|
+
module Encoding
|
4
3
|
class Base
|
5
|
-
|
6
|
-
|
7
|
-
|
4
|
+
|
5
|
+
include Delegatable
|
6
|
+
|
7
|
+
def encode(*args)
|
8
|
+
throw :unable_to_handle
|
8
9
|
end
|
9
|
-
|
10
|
+
|
10
11
|
end
|
11
|
-
|
12
12
|
end
|
13
13
|
end
|
@@ -5,7 +5,10 @@ module Dragonfly
|
|
5
5
|
|
6
6
|
class RMagickEncoder < Base
|
7
7
|
|
8
|
+
SUPPORTED_FORMATS = Magick.formats.select{|k,v| v =~ /.*rw./ }.map{|f| f.first.downcase }
|
9
|
+
|
8
10
|
def encode(image, format, encoding={})
|
11
|
+
throw :unable_to_handle unless SUPPORTED_FORMATS.include?(format.to_s)
|
9
12
|
encoded_image = Magick::Image.from_blob(image.data).first
|
10
13
|
encoded_image.format = format.to_s
|
11
14
|
encoded_image.to_blob
|
@@ -35,25 +35,31 @@ module Dragonfly
|
|
35
35
|
self
|
36
36
|
end
|
37
37
|
|
38
|
+
# As we create customly configured subclasses of this class, the console
|
39
|
+
# gives a confusing output when inspecting, so modify here
|
40
|
+
def inspect
|
41
|
+
super.sub('Class:','Custom Dragonfly::ExtendedTempObject:')
|
42
|
+
end
|
43
|
+
|
38
44
|
# Modify methods, public_methods and respond_to?, because method_missing
|
39
45
|
# allows methods from the analyser
|
40
46
|
|
41
47
|
def methods(*args)
|
42
|
-
(super + analyser.
|
48
|
+
(super + analyser.callable_methods).uniq
|
43
49
|
end
|
44
50
|
|
45
51
|
def public_methods(*args)
|
46
|
-
(super + analyser.
|
52
|
+
(super + analyser.callable_methods).uniq
|
47
53
|
end
|
48
54
|
|
49
55
|
def respond_to?(method)
|
50
|
-
super || analyser.
|
56
|
+
super || analyser.has_callable_method?(method)
|
51
57
|
end
|
52
58
|
|
53
59
|
private
|
54
60
|
|
55
61
|
def method_missing(method, *args, &block)
|
56
|
-
if analyser.
|
62
|
+
if analyser.has_callable_method?(method)
|
57
63
|
# Define the method so we don't use method_missing next time
|
58
64
|
instance_var = "@#{method}"
|
59
65
|
self.class.class_eval do
|
@@ -75,15 +81,15 @@ module Dragonfly
|
|
75
81
|
end
|
76
82
|
|
77
83
|
def analyser
|
78
|
-
app.
|
84
|
+
app.analysers
|
79
85
|
end
|
80
86
|
|
81
87
|
def processor
|
82
|
-
app.
|
88
|
+
app.processors
|
83
89
|
end
|
84
90
|
|
85
91
|
def encoder
|
86
|
-
app.
|
92
|
+
app.encoders
|
87
93
|
end
|
88
94
|
|
89
95
|
def parameters_class
|
data/lib/dragonfly/parameters.rb
CHANGED
@@ -17,6 +17,15 @@ module Dragonfly
|
|
17
17
|
configurable_attr :default_format
|
18
18
|
configurable_attr :default_encoding, {}
|
19
19
|
|
20
|
+
def new_with_defaults(attributes={})
|
21
|
+
new({
|
22
|
+
:processing_method => default_processing_method,
|
23
|
+
:processing_options => default_processing_options,
|
24
|
+
:format => default_format,
|
25
|
+
:encoding => default_encoding
|
26
|
+
}.merge(attributes))
|
27
|
+
end
|
28
|
+
|
20
29
|
def add_shortcut(*args, &block)
|
21
30
|
if block
|
22
31
|
block_shortcuts_of_length(args.length).unshift([args, block])
|
@@ -39,7 +48,7 @@ module Dragonfly
|
|
39
48
|
configuration_method :hash_from_shortcut
|
40
49
|
|
41
50
|
def from_shortcut(*args)
|
42
|
-
|
51
|
+
new_with_defaults(hash_from_shortcut(*args))
|
43
52
|
end
|
44
53
|
|
45
54
|
def from_args(*args)
|
@@ -96,17 +105,18 @@ module Dragonfly
|
|
96
105
|
|
97
106
|
# Instance methods
|
98
107
|
|
99
|
-
attr_accessor :uid, :processing_method, :processing_options, :format, :encoding
|
100
|
-
|
101
108
|
def initialize(attributes={})
|
102
109
|
attributes = attributes.dup
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
110
|
+
self.uid = attributes.delete(:uid)
|
111
|
+
self.processing_method = attributes.delete(:processing_method)
|
112
|
+
self.processing_options = attributes.delete(:processing_options) || {}
|
113
|
+
self.format = attributes.delete(:format)
|
114
|
+
self.encoding = attributes.delete(:encoding) || {}
|
107
115
|
raise ArgumentError, "Parameters doesn't recognise the following parameters: #{attributes.keys.join(', ')}" if attributes.any?
|
108
116
|
end
|
109
117
|
|
118
|
+
attr_accessor :uid, :processing_method, :processing_options, :format, :encoding
|
119
|
+
|
110
120
|
def [](attribute)
|
111
121
|
send(attribute)
|
112
122
|
end
|
@@ -137,10 +147,6 @@ module Dragonfly
|
|
137
147
|
}
|
138
148
|
end
|
139
149
|
|
140
|
-
def validate!
|
141
|
-
raise InvalidParameters, "Parameters requires that at least the uid and the format are set" if uid.nil? || format.nil?
|
142
|
-
end
|
143
|
-
|
144
150
|
private
|
145
151
|
|
146
152
|
def to_sorted_array
|