dragonfly 0.1.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.
- data/.gitignore +7 -0
- data/LICENSE +20 -0
- data/README.markdown +95 -0
- data/Rakefile +60 -0
- data/VERSION +1 -0
- data/config.rb +7 -0
- data/config.ru +10 -0
- data/dragonfly-rails.gemspec +41 -0
- data/dragonfly.gemspec +137 -0
- data/features/dragonfly.feature +38 -0
- data/features/steps/common_steps.rb +8 -0
- data/features/steps/dragonfly_steps.rb +39 -0
- data/features/support/env.rb +25 -0
- data/features/support/image_helpers.rb +9 -0
- data/generators/dragonfly_app/USAGE +16 -0
- data/generators/dragonfly_app/dragonfly_app_generator.rb +54 -0
- data/generators/dragonfly_app/templates/custom_processing.erb +13 -0
- data/generators/dragonfly_app/templates/initializer.erb +7 -0
- data/generators/dragonfly_app/templates/metal_file.erb +32 -0
- data/irbrc.rb +20 -0
- data/lib/dragonfly/active_record_extensions/attachment.rb +117 -0
- data/lib/dragonfly/active_record_extensions/class_methods.rb +41 -0
- data/lib/dragonfly/active_record_extensions/instance_methods.rb +28 -0
- data/lib/dragonfly/active_record_extensions/validations.rb +19 -0
- data/lib/dragonfly/active_record_extensions.rb +12 -0
- data/lib/dragonfly/analysis/analyser.rb +45 -0
- data/lib/dragonfly/analysis/base.rb +10 -0
- data/lib/dragonfly/analysis/r_magick_analyser.rb +40 -0
- data/lib/dragonfly/app.rb +85 -0
- data/lib/dragonfly/app_configuration.rb +9 -0
- data/lib/dragonfly/configurable.rb +113 -0
- data/lib/dragonfly/core_ext/object.rb +8 -0
- data/lib/dragonfly/data_storage/base.rb +19 -0
- data/lib/dragonfly/data_storage/file_data_store.rb +72 -0
- data/lib/dragonfly/data_storage.rb +9 -0
- data/lib/dragonfly/encoding/base.rb +13 -0
- data/lib/dragonfly/encoding/r_magick_encoder.rb +17 -0
- data/lib/dragonfly/extended_temp_object.rb +94 -0
- data/lib/dragonfly/middleware.rb +27 -0
- data/lib/dragonfly/parameters.rb +152 -0
- data/lib/dragonfly/processing/processor.rb +14 -0
- data/lib/dragonfly/processing/r_magick_processor.rb +71 -0
- data/lib/dragonfly/r_magick_configuration.rb +47 -0
- data/lib/dragonfly/rails/images.rb +20 -0
- data/lib/dragonfly/temp_object.rb +118 -0
- data/lib/dragonfly/url_handler.rb +148 -0
- data/lib/dragonfly.rb +33 -0
- data/samples/beach.png +0 -0
- data/samples/egg.png +0 -0
- data/samples/round.gif +0 -0
- data/samples/taj.jpg +0 -0
- data/spec/argument_matchers.rb +29 -0
- data/spec/dragonfly/active_record_extensions/attachment_spec.rb +8 -0
- data/spec/dragonfly/active_record_extensions/initializer.rb +1 -0
- data/spec/dragonfly/active_record_extensions/migration.rb +21 -0
- data/spec/dragonfly/active_record_extensions/model_spec.rb +400 -0
- data/spec/dragonfly/active_record_extensions/models.rb +2 -0
- data/spec/dragonfly/active_record_extensions/spec_helper.rb +23 -0
- data/spec/dragonfly/analysis/analyser_spec.rb +85 -0
- data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +35 -0
- data/spec/dragonfly/app_spec.rb +69 -0
- data/spec/dragonfly/configurable_spec.rb +193 -0
- data/spec/dragonfly/data_storage/data_store_spec.rb +47 -0
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +93 -0
- data/spec/dragonfly/extended_temp_object_spec.rb +67 -0
- data/spec/dragonfly/middleware_spec.rb +44 -0
- data/spec/dragonfly/parameters_spec.rb +293 -0
- data/spec/dragonfly/processing/rmagick_processor_spec.rb +148 -0
- data/spec/dragonfly/temp_object_spec.rb +233 -0
- data/spec/dragonfly/url_handler_spec.rb +246 -0
- data/spec/dragonfly_spec.rb +4 -0
- data/spec/image_matchers.rb +31 -0
- data/spec/simple_matchers.rb +14 -0
- data/spec/spec_helper.rb +19 -0
- metadata +160 -0
@@ -0,0 +1,117 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module ActiveRecordExtensions
|
3
|
+
|
4
|
+
class PendingUID; def to_s; 'PENDING'; end; end
|
5
|
+
|
6
|
+
class Attachment
|
7
|
+
|
8
|
+
def initialize(app, parent_model, attribute_name)
|
9
|
+
@app, @parent_model, @attribute_name = app, parent_model, attribute_name
|
10
|
+
end
|
11
|
+
|
12
|
+
def assign(value)
|
13
|
+
if value.nil?
|
14
|
+
self.uid = nil
|
15
|
+
reset_magic_attributes
|
16
|
+
else
|
17
|
+
self.temp_object = app.create_object(value)
|
18
|
+
self.uid = PendingUID.new
|
19
|
+
set_magic_attributes
|
20
|
+
end
|
21
|
+
value
|
22
|
+
end
|
23
|
+
|
24
|
+
def destroy!
|
25
|
+
app.datastore.destroy(previous_uid) if previous_uid
|
26
|
+
rescue DataStorage::DataNotFound => e
|
27
|
+
app.log.warn("*** WARNING ***: tried to destroy data with uid #{previous_uid}, but got error: #{e}")
|
28
|
+
end
|
29
|
+
|
30
|
+
def fetch(*args)
|
31
|
+
app.fetch(uid, *args)
|
32
|
+
end
|
33
|
+
|
34
|
+
def save!
|
35
|
+
if changed?
|
36
|
+
destroy!
|
37
|
+
self.uid = app.datastore.store(temp_object)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def size
|
42
|
+
temp_object.size
|
43
|
+
end
|
44
|
+
|
45
|
+
def temp_object
|
46
|
+
if @temp_object
|
47
|
+
@temp_object
|
48
|
+
elsif been_persisted?
|
49
|
+
@temp_object = fetch
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def to_value
|
54
|
+
self if been_assigned?
|
55
|
+
end
|
56
|
+
|
57
|
+
def url(*args)
|
58
|
+
unless uid.nil? || uid.is_a?(PendingUID)
|
59
|
+
app.url_for(uid, *args)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
private
|
64
|
+
|
65
|
+
def been_assigned?
|
66
|
+
uid
|
67
|
+
end
|
68
|
+
|
69
|
+
def been_persisted?
|
70
|
+
uid && !uid.is_a?(PendingUID)
|
71
|
+
end
|
72
|
+
|
73
|
+
def changed?
|
74
|
+
parent_model.send("#{attribute_name}_uid_changed?")
|
75
|
+
end
|
76
|
+
|
77
|
+
def uid=(uid)
|
78
|
+
parent_model.send("#{attribute_name}_uid=", uid)
|
79
|
+
end
|
80
|
+
|
81
|
+
def uid
|
82
|
+
parent_model.send("#{attribute_name}_uid")
|
83
|
+
end
|
84
|
+
|
85
|
+
def previous_uid
|
86
|
+
parent_model.send("#{attribute_name}_uid_was")
|
87
|
+
end
|
88
|
+
|
89
|
+
attr_reader :app, :parent_model, :attribute_name
|
90
|
+
|
91
|
+
attr_writer :temp_object
|
92
|
+
|
93
|
+
def analyser
|
94
|
+
app.analyser
|
95
|
+
end
|
96
|
+
|
97
|
+
def magic_attributes
|
98
|
+
parent_model.class.column_names.select { |name|
|
99
|
+
name =~ /^#{attribute_name}_(.+)$/ &&
|
100
|
+
analyser.has_analysis_method?($1) || $1 == 'size'
|
101
|
+
}
|
102
|
+
end
|
103
|
+
|
104
|
+
def set_magic_attributes
|
105
|
+
magic_attributes.each do |attribute|
|
106
|
+
method = attribute.sub("#{attribute_name}_", '')
|
107
|
+
parent_model.send("#{attribute}=", temp_object.send(method))
|
108
|
+
end
|
109
|
+
end
|
110
|
+
|
111
|
+
def reset_magic_attributes
|
112
|
+
magic_attributes.each{|attribute| parent_model.send("#{attribute}=", nil) }
|
113
|
+
end
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module ActiveRecordExtensions
|
3
|
+
module ClassMethods
|
4
|
+
|
5
|
+
include Validations
|
6
|
+
|
7
|
+
def register_dragonfly_app(accessor_prefix, app)
|
8
|
+
metaclass.class_eval do
|
9
|
+
|
10
|
+
# Defines e.g. 'image_accessor' for any activerecord class body
|
11
|
+
define_method "#{accessor_prefix}_accessor" do |attribute|
|
12
|
+
|
13
|
+
before_save :save_attached_files unless before_save_callback_chain.find(:save_attached_files)
|
14
|
+
before_destroy :destroy_attached_files unless before_destroy_callback_chain.find(:destroy_attached_files)
|
15
|
+
|
16
|
+
# Register the new attribute
|
17
|
+
dragonfly_apps_for_attributes[attribute] = app
|
18
|
+
|
19
|
+
# Define the setter for the attribute
|
20
|
+
define_method "#{attribute}=" do |value|
|
21
|
+
attachments[attribute].assign(value)
|
22
|
+
end
|
23
|
+
|
24
|
+
# Define the getter for the attribute
|
25
|
+
define_method attribute do
|
26
|
+
attachments[attribute].to_value
|
27
|
+
end
|
28
|
+
|
29
|
+
end
|
30
|
+
|
31
|
+
end
|
32
|
+
app
|
33
|
+
end
|
34
|
+
|
35
|
+
def dragonfly_apps_for_attributes
|
36
|
+
@dragonfly_apps_for_attributes ||= {}
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module ActiveRecordExtensions
|
3
|
+
module InstanceMethods
|
4
|
+
|
5
|
+
def attachments
|
6
|
+
@attachments ||= self.class.dragonfly_apps_for_attributes.inject({}) do |hash, (attribute, app)|
|
7
|
+
hash[attribute] = Attachment.new(app, self, attribute)
|
8
|
+
hash
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def save_attached_files
|
15
|
+
attachments.each do |attribute, attachment|
|
16
|
+
attachment.save!
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def destroy_attached_files
|
21
|
+
attachments.each do |attribute, attachment|
|
22
|
+
attachment.destroy!
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module ActiveRecordExtensions
|
3
|
+
module Validations
|
4
|
+
|
5
|
+
def validates_mime_type_of(*args)
|
6
|
+
opts = args.last
|
7
|
+
raise ArgumentError, "you must provide either :in => [(mime-types)] or :as => '(mime-type)' to validates_mime_type_of" unless opts.is_a?(::Hash) &&
|
8
|
+
(allowed_mime_types = opts[:in] || [opts[:as]])
|
9
|
+
validates_each(*args) do |record, attr, attachment|
|
10
|
+
if attachment
|
11
|
+
mime_type = attachment.temp_object.mime_type
|
12
|
+
record.errors.add attr, "doesn't have the correct MIME-type. It needs to be #{'one of ' if allowed_mime_types.length > 1}'#{allowed_mime_types.join('\', \'')}', but was '#{mime_type || 'unknown'}'" unless allowed_mime_types.include?(mime_type)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module Analysis
|
3
|
+
class Analyser
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
@analysers = []
|
7
|
+
end
|
8
|
+
|
9
|
+
include Configurable
|
10
|
+
|
11
|
+
def register(analyser)
|
12
|
+
analysers.unshift(analyser)
|
13
|
+
end
|
14
|
+
configuration_method :register
|
15
|
+
|
16
|
+
def mime_type(temp_object)
|
17
|
+
analysers.each do |analyser|
|
18
|
+
mime_type = analyser.mime_type(temp_object)
|
19
|
+
return mime_type if mime_type
|
20
|
+
end
|
21
|
+
nil
|
22
|
+
end
|
23
|
+
|
24
|
+
def analysis_methods
|
25
|
+
analysers.map{|a| a.public_methods(false) }.flatten.uniq
|
26
|
+
end
|
27
|
+
|
28
|
+
def has_analysis_method?(method)
|
29
|
+
analysis_methods.include?(method.to_s)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
attr_reader :analysers
|
35
|
+
|
36
|
+
def method_missing(meth, *args)
|
37
|
+
analysers.each do |analyser|
|
38
|
+
return analyser.send(meth, *args) if analyser.respond_to?(meth)
|
39
|
+
end
|
40
|
+
super
|
41
|
+
end
|
42
|
+
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
require 'rmagick'
|
2
|
+
require 'mime/types'
|
3
|
+
|
4
|
+
module Dragonfly
|
5
|
+
module Analysis
|
6
|
+
|
7
|
+
class RMagickAnalyser < Base
|
8
|
+
|
9
|
+
def width(image)
|
10
|
+
rmagick_image(image).columns
|
11
|
+
end
|
12
|
+
|
13
|
+
def height(image)
|
14
|
+
rmagick_image(image).rows
|
15
|
+
end
|
16
|
+
|
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
|
+
def depth(image)
|
23
|
+
rmagick_image(image).depth
|
24
|
+
end
|
25
|
+
|
26
|
+
def number_of_colours(image)
|
27
|
+
rmagick_image(image).number_colors
|
28
|
+
end
|
29
|
+
alias number_of_colors number_of_colours
|
30
|
+
|
31
|
+
private
|
32
|
+
|
33
|
+
def rmagick_image(image)
|
34
|
+
Magick::Image.from_blob(image.data).first
|
35
|
+
end
|
36
|
+
|
37
|
+
end
|
38
|
+
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
require 'logger'
|
2
|
+
require 'forwardable'
|
3
|
+
|
4
|
+
module Dragonfly
|
5
|
+
class App
|
6
|
+
|
7
|
+
class << self
|
8
|
+
|
9
|
+
private :new # Hide 'new' - need to use 'instance'
|
10
|
+
|
11
|
+
def instance(name)
|
12
|
+
apps[name] ||= new
|
13
|
+
end
|
14
|
+
|
15
|
+
alias [] instance
|
16
|
+
|
17
|
+
private
|
18
|
+
|
19
|
+
def apps
|
20
|
+
@apps ||= {}
|
21
|
+
end
|
22
|
+
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize
|
26
|
+
@analyser = Analysis::Analyser.new
|
27
|
+
@processor = Processing::Processor.new
|
28
|
+
@parameters_class = Class.new(Parameters)
|
29
|
+
@url_handler = UrlHandler.new(@parameters_class)
|
30
|
+
initialize_temp_object_class
|
31
|
+
end
|
32
|
+
|
33
|
+
attr_reader :analyser,
|
34
|
+
:processor,
|
35
|
+
:encoder,
|
36
|
+
:url_handler,
|
37
|
+
:parameters_class,
|
38
|
+
:temp_object_class
|
39
|
+
|
40
|
+
alias parameters parameters_class
|
41
|
+
|
42
|
+
# Just for convenience so the user doesn't have to use url_handler
|
43
|
+
extend Forwardable
|
44
|
+
def_delegator :url_handler, :url_for
|
45
|
+
|
46
|
+
include Configurable
|
47
|
+
|
48
|
+
configurable_attr :datastore do DataStorage::FileDataStore.new end
|
49
|
+
configurable_attr :encoder do Encoding::Base.new end
|
50
|
+
configurable_attr :log do Logger.new('/var/tmp/dragonfly.log') end
|
51
|
+
configurable_attr :cache_duration, 3000
|
52
|
+
|
53
|
+
def call(env)
|
54
|
+
parameters = url_handler.url_to_parameters(env['PATH_INFO'], env['QUERY_STRING'])
|
55
|
+
temp_object = fetch(parameters.uid, parameters)
|
56
|
+
[200, {
|
57
|
+
"Content-Type" => temp_object.mime_type,
|
58
|
+
"Content-Length" => temp_object.size.to_s,
|
59
|
+
"ETag" => parameters.unique_signature,
|
60
|
+
"Cache-Control" => "public, max-age=#{cache_duration}"
|
61
|
+
}, temp_object]
|
62
|
+
rescue UrlHandler::IncorrectSHA, UrlHandler::SHANotGiven => e
|
63
|
+
[400, {"Content-Type" => "text/plain"}, [e.message]]
|
64
|
+
rescue UrlHandler::UnknownUrl, DataStorage::DataNotFound => e
|
65
|
+
[404, {"Content-Type" => 'text/plain'}, [e.message]]
|
66
|
+
end
|
67
|
+
|
68
|
+
def fetch(uid, *args)
|
69
|
+
temp_object = temp_object_class.new(datastore.retrieve(uid))
|
70
|
+
temp_object.transform(*args)
|
71
|
+
end
|
72
|
+
|
73
|
+
def create_object(initialization_object)
|
74
|
+
temp_object_class.new(initialization_object)
|
75
|
+
end
|
76
|
+
|
77
|
+
private
|
78
|
+
|
79
|
+
def initialize_temp_object_class
|
80
|
+
@temp_object_class = Class.new(ExtendedTempObject)
|
81
|
+
@temp_object_class.app = self
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module Configurable
|
3
|
+
|
4
|
+
# Exceptions
|
5
|
+
class BadConfigAttribute < StandardError; end
|
6
|
+
|
7
|
+
def self.included(klass)
|
8
|
+
klass.class_eval do
|
9
|
+
include Configurable::InstanceMethods
|
10
|
+
extend Configurable::ClassMethods
|
11
|
+
|
12
|
+
# These aren't included in InstanceMethods because we need access to 'klass'
|
13
|
+
# We can't just put them into InstanceMethods and use 'self.class' because
|
14
|
+
# this won't always point to the class in which we've included Configurable,
|
15
|
+
# e.g. if we've included it in an eigenclasse
|
16
|
+
define_method :configuration_hash do
|
17
|
+
@configuration_hash ||= klass.default_configuration.dup
|
18
|
+
end
|
19
|
+
private :configuration_hash
|
20
|
+
|
21
|
+
define_method :configuration_methods do
|
22
|
+
klass.configuration_methods
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
module InstanceMethods
|
29
|
+
|
30
|
+
def configure(&blk)
|
31
|
+
yield ConfigurationProxy.new(self)
|
32
|
+
end
|
33
|
+
|
34
|
+
def configure_with(configurer)
|
35
|
+
configurer.apply_configuration(self)
|
36
|
+
end
|
37
|
+
|
38
|
+
def configuration
|
39
|
+
configuration_hash.dup
|
40
|
+
end
|
41
|
+
|
42
|
+
def has_configuration_method?(method_name)
|
43
|
+
configuration_methods.include?(method_name.to_sym)
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
47
|
+
|
48
|
+
module ClassMethods
|
49
|
+
|
50
|
+
def default_configuration
|
51
|
+
@default_configuration ||= {}
|
52
|
+
end
|
53
|
+
|
54
|
+
def configuration_methods
|
55
|
+
@configuration_methods ||= []
|
56
|
+
end
|
57
|
+
|
58
|
+
private
|
59
|
+
|
60
|
+
def configurable_attr attribute, default=nil, &blk
|
61
|
+
default_configuration[attribute] = blk || default
|
62
|
+
|
63
|
+
# Define the reader
|
64
|
+
define_method(attribute) do
|
65
|
+
if configuration_hash[attribute].respond_to? :call
|
66
|
+
configuration_hash[attribute] = configuration_hash[attribute].call
|
67
|
+
end
|
68
|
+
configuration_hash[attribute]
|
69
|
+
end
|
70
|
+
|
71
|
+
# Define the writer
|
72
|
+
define_method("#{attribute}=") do |value|
|
73
|
+
configuration_hash[attribute] = value
|
74
|
+
end
|
75
|
+
|
76
|
+
configuration_method attribute
|
77
|
+
configuration_method "#{attribute}="
|
78
|
+
end
|
79
|
+
|
80
|
+
def configuration_method(*method_names)
|
81
|
+
configuration_methods.push(*method_names.map{|n| n.to_sym })
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
class ConfigurationProxy
|
87
|
+
|
88
|
+
def initialize(owner)
|
89
|
+
@owner = owner
|
90
|
+
end
|
91
|
+
|
92
|
+
def method_missing(method_name, *args, &block)
|
93
|
+
if owner.has_configuration_method?(method_name)
|
94
|
+
owner.send(method_name, *args, &block)
|
95
|
+
elsif nested_configurable?(method_name, *args)
|
96
|
+
owner.send(method_name, *args).configure(&block)
|
97
|
+
else
|
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
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
attr_reader :owner
|
105
|
+
|
106
|
+
def nested_configurable?(method, *args)
|
107
|
+
owner.respond_to?(method) && owner.send(method, *args).is_a?(Configurable)
|
108
|
+
end
|
109
|
+
|
110
|
+
end
|
111
|
+
|
112
|
+
end
|
113
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module DataStorage
|
3
|
+
class Base
|
4
|
+
|
5
|
+
def store(temp_object)
|
6
|
+
raise NotImplementedError
|
7
|
+
end
|
8
|
+
|
9
|
+
def retrieve(uid)
|
10
|
+
raise NotImplementedError
|
11
|
+
end
|
12
|
+
|
13
|
+
def destroy(uid)
|
14
|
+
raise NotImplementedError
|
15
|
+
end
|
16
|
+
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
module Dragonfly
|
2
|
+
module DataStorage
|
3
|
+
|
4
|
+
class FileDataStore < Base
|
5
|
+
|
6
|
+
include Configurable
|
7
|
+
|
8
|
+
configurable_attr :root_path, '/var/tmp/dragonfly'
|
9
|
+
|
10
|
+
def store(temp_object)
|
11
|
+
|
12
|
+
suffix = if temp_object.name.blank?
|
13
|
+
'file'
|
14
|
+
else
|
15
|
+
temp_object.name.sub(/\.[^.]*?$/, '')
|
16
|
+
end
|
17
|
+
relative_path = relative_storage_path(suffix)
|
18
|
+
|
19
|
+
begin
|
20
|
+
while File.exist?(storage_path = absolute_storage_path(relative_path))
|
21
|
+
relative_path = increment_path(relative_path)
|
22
|
+
end
|
23
|
+
FileUtils.mkdir_p File.dirname(storage_path) unless File.exist?(storage_path)
|
24
|
+
FileUtils.cp temp_object.path, storage_path
|
25
|
+
rescue Errno::EACCES => e
|
26
|
+
raise UnableToStore, e.message
|
27
|
+
end
|
28
|
+
|
29
|
+
relative_path
|
30
|
+
end
|
31
|
+
|
32
|
+
def retrieve(relative_path)
|
33
|
+
begin
|
34
|
+
File.new(absolute_storage_path(relative_path))
|
35
|
+
rescue Errno::ENOENT => e
|
36
|
+
raise DataNotFound, e.message
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def destroy(relative_path)
|
41
|
+
FileUtils.rm absolute_storage_path(relative_path)
|
42
|
+
containing_directory = Pathname.new(relative_path).dirname
|
43
|
+
containing_directory.ascend do |relative_dir|
|
44
|
+
dir = absolute_storage_path(relative_dir)
|
45
|
+
FileUtils.rmdir dir if directory_empty?(dir)
|
46
|
+
end
|
47
|
+
rescue Errno::ENOENT => e
|
48
|
+
raise DataNotFound, e.message
|
49
|
+
end
|
50
|
+
|
51
|
+
private
|
52
|
+
|
53
|
+
def increment_path(path)
|
54
|
+
path.sub(/(_(\d+))?$/){ $1 ? "_#{$2.to_i+1}" : '_2' }
|
55
|
+
end
|
56
|
+
|
57
|
+
def relative_storage_path(suffix)
|
58
|
+
"#{Time.now.strftime '%Y/%m/%d/%H%M%S'}_#{suffix}"
|
59
|
+
end
|
60
|
+
|
61
|
+
def absolute_storage_path(relative_path)
|
62
|
+
File.join(root_path, relative_path)
|
63
|
+
end
|
64
|
+
|
65
|
+
def directory_empty?(path)
|
66
|
+
Dir.entries(path) == ['.','..']
|
67
|
+
end
|
68
|
+
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'rmagick'
|
2
|
+
|
3
|
+
module Dragonfly
|
4
|
+
module Encoding
|
5
|
+
|
6
|
+
class RMagickEncoder < Base
|
7
|
+
|
8
|
+
def encode(image, format, encoding={})
|
9
|
+
encoded_image = Magick::Image.from_blob(image.data).first
|
10
|
+
encoded_image.format = format.to_s
|
11
|
+
encoded_image.to_blob
|
12
|
+
end
|
13
|
+
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|