fog-dragonfly 0.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.specopts +2 -0
- data/.yardopts +23 -0
- data/Gemfile +23 -0
- data/Gemfile.rails.2.3.5 +14 -0
- data/History.md +266 -0
- data/LICENSE +20 -0
- data/README.md +88 -0
- data/Rakefile +92 -0
- data/VERSION +1 -0
- data/config.ru +13 -0
- data/docs.watchr +1 -0
- data/dragonfly.gemspec +293 -0
- data/extra_docs/Analysers.md +108 -0
- data/extra_docs/Caching.md +23 -0
- data/extra_docs/Configuration.md +138 -0
- data/extra_docs/DataStorage.md +136 -0
- data/extra_docs/Encoding.md +96 -0
- data/extra_docs/GeneralUsage.md +121 -0
- data/extra_docs/Generators.md +102 -0
- data/extra_docs/Heroku.md +50 -0
- data/extra_docs/Index.md +36 -0
- data/extra_docs/MimeTypes.md +40 -0
- data/extra_docs/Models.md +266 -0
- data/extra_docs/Mongo.md +45 -0
- data/extra_docs/Processing.md +130 -0
- data/extra_docs/Rack.md +52 -0
- data/extra_docs/Rails2.md +55 -0
- data/extra_docs/Rails3.md +62 -0
- data/extra_docs/Sinatra.md +25 -0
- data/extra_docs/URLs.md +169 -0
- data/features/3.0.3.feature +8 -0
- data/features/images.feature +47 -0
- data/features/no_processing.feature +14 -0
- data/features/rails_2.3.5.feature +7 -0
- data/features/steps/common_steps.rb +8 -0
- data/features/steps/dragonfly_steps.rb +66 -0
- data/features/steps/rails_steps.rb +39 -0
- data/features/support/env.rb +40 -0
- data/fixtures/files/app/models/album.rb +3 -0
- data/fixtures/files/app/views/albums/new.html.erb +4 -0
- data/fixtures/files/app/views/albums/show.html.erb +4 -0
- data/fixtures/files/config/initializers/dragonfly.rb +4 -0
- data/fixtures/files/features/manage_album_images.feature +12 -0
- data/fixtures/files/features/step_definitions/image_steps.rb +15 -0
- data/fixtures/files/features/support/paths.rb +15 -0
- data/fixtures/files/features/text_images.feature +7 -0
- data/fixtures/rails_2.3.5/template.rb +10 -0
- data/fixtures/rails_3.0.3/template.rb +20 -0
- data/irbrc.rb +17 -0
- data/lib/dragonfly.rb +45 -0
- data/lib/dragonfly/active_model_extensions.rb +13 -0
- data/lib/dragonfly/active_model_extensions/attachment.rb +169 -0
- data/lib/dragonfly/active_model_extensions/class_methods.rb +45 -0
- data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
- data/lib/dragonfly/active_model_extensions/validations.rb +37 -0
- data/lib/dragonfly/analyser.rb +59 -0
- data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
- data/lib/dragonfly/analysis/image_magick_analyser.rb +47 -0
- data/lib/dragonfly/analysis/r_magick_analyser.rb +63 -0
- data/lib/dragonfly/app.rb +182 -0
- data/lib/dragonfly/config/heroku.rb +19 -0
- data/lib/dragonfly/config/image_magick.rb +41 -0
- data/lib/dragonfly/config/r_magick.rb +46 -0
- data/lib/dragonfly/config/rails.rb +17 -0
- data/lib/dragonfly/configurable.rb +119 -0
- data/lib/dragonfly/core_ext/object.rb +8 -0
- data/lib/dragonfly/core_ext/string.rb +9 -0
- data/lib/dragonfly/core_ext/symbol.rb +9 -0
- data/lib/dragonfly/data_storage.rb +9 -0
- data/lib/dragonfly/data_storage/file_data_store.rb +114 -0
- data/lib/dragonfly/data_storage/mongo_data_store.rb +82 -0
- data/lib/dragonfly/data_storage/s3data_store.rb +115 -0
- data/lib/dragonfly/encoder.rb +13 -0
- data/lib/dragonfly/encoding/image_magick_encoder.rb +57 -0
- data/lib/dragonfly/encoding/r_magick_encoder.rb +61 -0
- data/lib/dragonfly/function_manager.rb +69 -0
- data/lib/dragonfly/generation/hash_with_css_style_keys.rb +23 -0
- data/lib/dragonfly/generation/image_magick_generator.rb +140 -0
- data/lib/dragonfly/generation/r_magick_generator.rb +155 -0
- data/lib/dragonfly/generator.rb +9 -0
- data/lib/dragonfly/image_magick_utils.rb +81 -0
- data/lib/dragonfly/job.rb +371 -0
- data/lib/dragonfly/job_builder.rb +39 -0
- data/lib/dragonfly/job_definitions.rb +26 -0
- data/lib/dragonfly/job_endpoint.rb +15 -0
- data/lib/dragonfly/loggable.rb +28 -0
- data/lib/dragonfly/middleware.rb +34 -0
- data/lib/dragonfly/processing/image_magick_processor.rb +99 -0
- data/lib/dragonfly/processing/r_magick_processor.rb +126 -0
- data/lib/dragonfly/processor.rb +9 -0
- data/lib/dragonfly/r_magick_utils.rb +48 -0
- data/lib/dragonfly/rails/images.rb +22 -0
- data/lib/dragonfly/response.rb +82 -0
- data/lib/dragonfly/routed_endpoint.rb +40 -0
- data/lib/dragonfly/serializer.rb +32 -0
- data/lib/dragonfly/simple_cache.rb +23 -0
- data/lib/dragonfly/simple_endpoint.rb +63 -0
- data/lib/dragonfly/temp_object.rb +220 -0
- data/samples/beach.png +0 -0
- data/samples/egg.png +0 -0
- data/samples/round.gif +0 -0
- data/samples/sample.docx +0 -0
- data/samples/taj.jpg +0 -0
- data/spec/argument_matchers.rb +19 -0
- data/spec/dragonfly/active_model_extensions/active_model_setup.rb +97 -0
- data/spec/dragonfly/active_model_extensions/active_record_setup.rb +85 -0
- data/spec/dragonfly/active_model_extensions/model_spec.rb +723 -0
- data/spec/dragonfly/active_model_extensions/spec_helper.rb +11 -0
- data/spec/dragonfly/analyser_spec.rb +123 -0
- data/spec/dragonfly/analysis/file_command_analyser_spec.rb +57 -0
- data/spec/dragonfly/analysis/image_magick_analyser_spec.rb +15 -0
- data/spec/dragonfly/analysis/r_magick_analyser_spec.rb +27 -0
- data/spec/dragonfly/analysis/shared_analyser_spec.rb +51 -0
- data/spec/dragonfly/app_spec.rb +280 -0
- data/spec/dragonfly/config/r_magick_spec.rb +25 -0
- data/spec/dragonfly/configurable_spec.rb +220 -0
- data/spec/dragonfly/core_ext/string_spec.rb +17 -0
- data/spec/dragonfly/core_ext/symbol_spec.rb +17 -0
- data/spec/dragonfly/data_storage/data_store_spec.rb +76 -0
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +169 -0
- data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +38 -0
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +94 -0
- data/spec/dragonfly/deprecation_spec.rb +20 -0
- data/spec/dragonfly/encoding/image_magick_encoder_spec.rb +41 -0
- data/spec/dragonfly/encoding/r_magick_encoder_spec.rb +37 -0
- data/spec/dragonfly/function_manager_spec.rb +154 -0
- data/spec/dragonfly/generation/hash_with_css_style_keys_spec.rb +24 -0
- data/spec/dragonfly/generation/image_magick_generator_spec.rb +12 -0
- data/spec/dragonfly/generation/r_magick_generator_spec.rb +24 -0
- data/spec/dragonfly/generation/shared_generator_spec.rb +91 -0
- data/spec/dragonfly/image_magick_utils_spec.rb +16 -0
- data/spec/dragonfly/job_builder_spec.rb +37 -0
- data/spec/dragonfly/job_definitions_spec.rb +35 -0
- data/spec/dragonfly/job_endpoint_spec.rb +120 -0
- data/spec/dragonfly/job_spec.rb +773 -0
- data/spec/dragonfly/loggable_spec.rb +80 -0
- data/spec/dragonfly/middleware_spec.rb +68 -0
- data/spec/dragonfly/processing/image_magick_processor_spec.rb +29 -0
- data/spec/dragonfly/processing/r_magick_processor_spec.rb +26 -0
- data/spec/dragonfly/processing/shared_processing_spec.rb +215 -0
- data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
- data/spec/dragonfly/serializer_spec.rb +61 -0
- data/spec/dragonfly/simple_cache_spec.rb +27 -0
- data/spec/dragonfly/simple_endpoint_spec.rb +89 -0
- data/spec/dragonfly/temp_object_spec.rb +352 -0
- data/spec/image_matchers.rb +47 -0
- data/spec/simple_matchers.rb +44 -0
- data/spec/spec_helper.rb +58 -0
- data/yard/handlers/configurable_attr_handler.rb +38 -0
- data/yard/setup.rb +15 -0
- data/yard/templates/default/fulldoc/html/css/common.css +107 -0
- data/yard/templates/default/layout/html/layout.erb +87 -0
- data/yard/templates/default/module/html/configuration_summary.erb +31 -0
- data/yard/templates/default/module/setup.rb +17 -0
- metadata +550 -0
|
@@ -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
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
module Dragonfly
|
|
2
|
+
class RoutedEndpoint
|
|
3
|
+
|
|
4
|
+
class NoRoutingParams < RuntimeError; end
|
|
5
|
+
|
|
6
|
+
def initialize(app, &block)
|
|
7
|
+
@app = app
|
|
8
|
+
@block = block
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def call(env)
|
|
12
|
+
params = symbolize_keys_of Rack::Request.new(env).params
|
|
13
|
+
job = @block.call(params.merge(routing_params(env)), @app)
|
|
14
|
+
Response.new(job, env).to_response
|
|
15
|
+
rescue Job::NoSHAGiven => e
|
|
16
|
+
[400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
|
|
17
|
+
rescue Job::IncorrectSHA => e
|
|
18
|
+
[400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
private
|
|
22
|
+
|
|
23
|
+
def routing_params(env)
|
|
24
|
+
env['rack.routing_args'] ||
|
|
25
|
+
env['action_dispatch.request.path_parameters'] ||
|
|
26
|
+
env['router.params'] ||
|
|
27
|
+
env['usher.params'] ||
|
|
28
|
+
env['dragonfly.params'] ||
|
|
29
|
+
raise(NoRoutingParams, "couldn't find any routing parameters in env #{env.inspect}")
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def symbolize_keys_of(hash)
|
|
33
|
+
hash.inject({}) do |h, (key, value)|
|
|
34
|
+
h[(key.to_sym rescue key) || key] = value
|
|
35
|
+
h
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
end
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# encoding: utf-8
|
|
2
|
+
require 'base64'
|
|
3
|
+
|
|
4
|
+
module Dragonfly
|
|
5
|
+
module Serializer
|
|
6
|
+
|
|
7
|
+
# Exceptions
|
|
8
|
+
class BadString < RuntimeError; end
|
|
9
|
+
|
|
10
|
+
extend self # So we can do Serializer.b64_encode, etc.
|
|
11
|
+
|
|
12
|
+
def b64_encode(string)
|
|
13
|
+
Base64.encode64(string).tr("\n=",'')
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def b64_decode(string)
|
|
17
|
+
padding_length = string.length % 4
|
|
18
|
+
Base64.decode64(string + '=' * padding_length)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def marshal_encode(object)
|
|
22
|
+
b64_encode(Marshal.dump(object))
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def marshal_decode(string)
|
|
26
|
+
Marshal.load(b64_decode(string))
|
|
27
|
+
rescue TypeError, ArgumentError => e
|
|
28
|
+
raise BadString, "couldn't decode #{string} - got #{e}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
module Dragonfly
|
|
2
|
+
class SimpleCache < Hash
|
|
3
|
+
|
|
4
|
+
def initialize(max_size)
|
|
5
|
+
@max_size = max_size
|
|
6
|
+
@keys = []
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
attr_reader :max_size
|
|
10
|
+
|
|
11
|
+
def []=(key, value)
|
|
12
|
+
if !has_key?(key)
|
|
13
|
+
@keys << key
|
|
14
|
+
if size == max_size
|
|
15
|
+
key_to_purge = @keys.shift
|
|
16
|
+
delete(key_to_purge)
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
end
|
|
23
|
+
end
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
module Dragonfly
|
|
2
|
+
class SimpleEndpoint
|
|
3
|
+
|
|
4
|
+
include Loggable
|
|
5
|
+
|
|
6
|
+
# Instance methods
|
|
7
|
+
|
|
8
|
+
def initialize(app)
|
|
9
|
+
@app = app
|
|
10
|
+
use_same_log_as(app)
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def call(env)
|
|
14
|
+
request = Rack::Request.new(env)
|
|
15
|
+
|
|
16
|
+
case request.path_info
|
|
17
|
+
when '', '/', app.url_path_prefix
|
|
18
|
+
dragonfly_response
|
|
19
|
+
else
|
|
20
|
+
job = Job.from_path(request.path_info, app)
|
|
21
|
+
job.validate_sha!(request['s']) if app.protect_from_dos_attacks
|
|
22
|
+
Response.new(job, env).to_response
|
|
23
|
+
end
|
|
24
|
+
rescue Serializer::BadString, Job::InvalidArray => e
|
|
25
|
+
log.warn(e.message)
|
|
26
|
+
[404, {'Content-Type' => 'text/plain'}, ['Not found']]
|
|
27
|
+
rescue Job::NoSHAGiven => e
|
|
28
|
+
[400, {"Content-Type" => 'text/plain'}, ["You need to give a SHA parameter"]]
|
|
29
|
+
rescue Job::IncorrectSHA => e
|
|
30
|
+
[400, {"Content-Type" => 'text/plain'}, ["The SHA parameter you gave (#{e}) is incorrect"]]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def required_params_for(job)
|
|
34
|
+
{'s' => job.sha}
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
private
|
|
38
|
+
|
|
39
|
+
attr_reader :app
|
|
40
|
+
|
|
41
|
+
def dragonfly_response
|
|
42
|
+
body = <<-DRAGONFLY
|
|
43
|
+
_o|o_
|
|
44
|
+
_~~---._( )_.---~~_
|
|
45
|
+
( . \\ / . )
|
|
46
|
+
`-.~--' |=| '--~.-'
|
|
47
|
+
_~-.~'" /|=|\\ "'~.-~_
|
|
48
|
+
( ./ |=| \\. )
|
|
49
|
+
`~~`"` |=| `"'ME"
|
|
50
|
+
|-|
|
|
51
|
+
<->
|
|
52
|
+
V
|
|
53
|
+
DRAGONFLY
|
|
54
|
+
[200, {
|
|
55
|
+
'Content-Type' => 'text/plain',
|
|
56
|
+
'Content-Size' => body.bytesize.to_s
|
|
57
|
+
},
|
|
58
|
+
[body]
|
|
59
|
+
]
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
@@ -0,0 +1,220 @@
|
|
|
1
|
+
require 'stringio'
|
|
2
|
+
require 'tempfile'
|
|
3
|
+
|
|
4
|
+
module Dragonfly
|
|
5
|
+
|
|
6
|
+
# A TempObject is used for HOLDING DATA.
|
|
7
|
+
# It's the thing that is passed between the datastore, the processor and the encoder, and is useful
|
|
8
|
+
# for separating how the data was created and how it is later accessed.
|
|
9
|
+
#
|
|
10
|
+
# You can initialize it various ways:
|
|
11
|
+
#
|
|
12
|
+
# temp_object = Dragonfly::TempObject.new('this is the content') # with a String
|
|
13
|
+
# temp_object = Dragonfly::TempObject.new(File.new('path/to/content')) # with a File
|
|
14
|
+
# temp_object = Dragonfly::TempObject.new(some_tempfile) # with a Tempfile
|
|
15
|
+
# temp_object = Dragonfly::TempObject.new(some_other_temp_object) # with another TempObject
|
|
16
|
+
#
|
|
17
|
+
# However, no matter how it was initialized, you can always access the data a number of ways:
|
|
18
|
+
#
|
|
19
|
+
# temp_object.data # returns a data string
|
|
20
|
+
# temp_object.file # returns a file object holding the data
|
|
21
|
+
# temp_object.path # returns a path for the file
|
|
22
|
+
#
|
|
23
|
+
# The data/file are created lazily, something which you may wish to take advantage of.
|
|
24
|
+
#
|
|
25
|
+
# For example, if a TempObject is initialized with a file, and temp_object.data is never called, then
|
|
26
|
+
# the data string will never be loaded into memory.
|
|
27
|
+
#
|
|
28
|
+
# Conversely, if the TempObject is initialized with a data string, and neither temp_object.file nor temp_object.path
|
|
29
|
+
# are ever called, then the filesystem will never be hit.
|
|
30
|
+
#
|
|
31
|
+
class TempObject
|
|
32
|
+
|
|
33
|
+
# Class configuration
|
|
34
|
+
class << self
|
|
35
|
+
|
|
36
|
+
include Configurable
|
|
37
|
+
configurable_attr :block_size, 8192
|
|
38
|
+
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
# Instance Methods
|
|
42
|
+
|
|
43
|
+
def initialize(obj, opts={})
|
|
44
|
+
opts ||= {} # in case it's nil
|
|
45
|
+
initialize_from_object!(obj)
|
|
46
|
+
validate_options!(opts)
|
|
47
|
+
extract_attributes_from(opts)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def data
|
|
51
|
+
@data ||= initialized_data || file{|f| f.read }
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def tempfile
|
|
55
|
+
@tempfile ||= begin
|
|
56
|
+
case initialized_with
|
|
57
|
+
when :tempfile
|
|
58
|
+
@tempfile = initialized_tempfile
|
|
59
|
+
@tempfile.close
|
|
60
|
+
when :data
|
|
61
|
+
@tempfile = new_tempfile(initialized_data)
|
|
62
|
+
when :file
|
|
63
|
+
@tempfile = copy_to_tempfile(initialized_file.path)
|
|
64
|
+
end
|
|
65
|
+
@tempfile
|
|
66
|
+
end
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def file(&block)
|
|
70
|
+
f = tempfile.open
|
|
71
|
+
tempfile.binmode
|
|
72
|
+
if block_given?
|
|
73
|
+
ret = yield f
|
|
74
|
+
tempfile.close
|
|
75
|
+
else
|
|
76
|
+
ret = f
|
|
77
|
+
end
|
|
78
|
+
ret
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def path
|
|
82
|
+
tempfile.path
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def size
|
|
86
|
+
if initialized_data
|
|
87
|
+
initialized_data.bytesize
|
|
88
|
+
else
|
|
89
|
+
File.size(path)
|
|
90
|
+
end
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
attr_accessor :name, :format
|
|
94
|
+
alias _format format
|
|
95
|
+
attr_writer :meta
|
|
96
|
+
|
|
97
|
+
def meta
|
|
98
|
+
@meta ||= {}
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
def basename
|
|
102
|
+
File.basename(name, '.*') if name
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def ext
|
|
106
|
+
File.extname(name)[/\.(.*)/, 1] if name
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def each(&block)
|
|
110
|
+
to_io do |io|
|
|
111
|
+
while part = io.read(block_size)
|
|
112
|
+
yield part
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def to_file(path)
|
|
118
|
+
if initialized_data
|
|
119
|
+
File.open(path, 'wb'){|f| f.write(initialized_data) }
|
|
120
|
+
else
|
|
121
|
+
FileUtils.cp(self.path, path)
|
|
122
|
+
end
|
|
123
|
+
File.new(path, 'rb')
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def to_io(&block)
|
|
127
|
+
if initialized_data
|
|
128
|
+
StringIO.open(initialized_data, 'rb', &block)
|
|
129
|
+
else
|
|
130
|
+
file(&block)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
134
|
+
def attributes
|
|
135
|
+
{
|
|
136
|
+
:name => name,
|
|
137
|
+
:meta => meta,
|
|
138
|
+
:format => format
|
|
139
|
+
}
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def extract_attributes_from(hash)
|
|
143
|
+
self.name = hash.delete(:name) unless hash[:name].blank?
|
|
144
|
+
self.format = hash.delete(:format) unless hash[:format].blank?
|
|
145
|
+
self.meta.merge!(hash.delete(:meta)) unless hash[:meta].blank?
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def inspect
|
|
149
|
+
content_string = case initialized_with
|
|
150
|
+
when :data
|
|
151
|
+
data_string = size > 20 ? "#{initialized_data[0..20]}..." : initialized_data
|
|
152
|
+
"data=#{data_string.inspect}"
|
|
153
|
+
when :file then "file=#{initialized_file.inspect}"
|
|
154
|
+
when :tempfile then "tempfile=#{initialized_tempfile.inspect}"
|
|
155
|
+
end
|
|
156
|
+
to_s.sub(/>$/, " #{content_string}, @meta=#{@meta.inspect}, @name=#{@name.inspect} >")
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
protected
|
|
160
|
+
|
|
161
|
+
attr_accessor :initialized_data, :initialized_tempfile, :initialized_file
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def initialize_from_object!(obj)
|
|
166
|
+
if obj.is_a? TempObject
|
|
167
|
+
@initialized_data = obj.initialized_data
|
|
168
|
+
@initialized_tempfile = copy_to_tempfile(obj.initialized_tempfile.path) if obj.initialized_tempfile
|
|
169
|
+
@initialized_file = obj.initialized_file
|
|
170
|
+
elsif obj.is_a? String
|
|
171
|
+
@initialized_data = obj
|
|
172
|
+
elsif obj.is_a? Tempfile
|
|
173
|
+
@initialized_tempfile = obj
|
|
174
|
+
elsif obj.is_a? File
|
|
175
|
+
@initialized_file = obj
|
|
176
|
+
self.name = File.basename(obj.path)
|
|
177
|
+
elsif obj.respond_to?(:tempfile)
|
|
178
|
+
@initialized_tempfile = obj.tempfile
|
|
179
|
+
else
|
|
180
|
+
raise ArgumentError, "#{self.class.name} must be initialized with a String, a File, a Tempfile, another TempObject, or something that responds to .tempfile"
|
|
181
|
+
end
|
|
182
|
+
self.name = obj.original_filename if obj.respond_to?(:original_filename)
|
|
183
|
+
end
|
|
184
|
+
|
|
185
|
+
def initialized_with
|
|
186
|
+
if initialized_tempfile
|
|
187
|
+
:tempfile
|
|
188
|
+
elsif initialized_data
|
|
189
|
+
:data
|
|
190
|
+
elsif initialized_file
|
|
191
|
+
:file
|
|
192
|
+
end
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def block_size
|
|
196
|
+
self.class.block_size
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
def copy_to_tempfile(path)
|
|
200
|
+
tempfile = new_tempfile
|
|
201
|
+
FileUtils.cp File.expand_path(path), tempfile.path
|
|
202
|
+
tempfile
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
def validate_options!(opts)
|
|
206
|
+
valid_keys = [:name, :meta, :format]
|
|
207
|
+
invalid_keys = opts.keys - valid_keys
|
|
208
|
+
raise ArgumentError, "Unrecognised options #{invalid_keys.inspect}" if invalid_keys.any?
|
|
209
|
+
end
|
|
210
|
+
|
|
211
|
+
def new_tempfile(content=nil)
|
|
212
|
+
tempfile = Tempfile.new('dragonfly')
|
|
213
|
+
tempfile.binmode
|
|
214
|
+
tempfile.write(content) if content
|
|
215
|
+
tempfile.close
|
|
216
|
+
tempfile
|
|
217
|
+
end
|
|
218
|
+
|
|
219
|
+
end
|
|
220
|
+
end
|