oahu-dragonfly 0.8.2
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/.rspec +1 -0
- data/.yardopts +24 -0
- data/Gemfile +30 -0
- data/History.md +323 -0
- data/LICENSE +20 -0
- data/README.md +88 -0
- data/Rakefile +50 -0
- data/VERSION +1 -0
- data/config.ru +14 -0
- data/docs.watchr +1 -0
- data/dragonfly.gemspec +297 -0
- data/extra_docs/Analysers.md +66 -0
- data/extra_docs/Caching.md +23 -0
- data/extra_docs/Configuration.md +124 -0
- data/extra_docs/Couch.md +49 -0
- data/extra_docs/DataStorage.md +153 -0
- data/extra_docs/Encoding.md +67 -0
- data/extra_docs/GeneralUsage.md +121 -0
- data/extra_docs/Generators.md +60 -0
- data/extra_docs/Heroku.md +50 -0
- data/extra_docs/ImageMagick.md +125 -0
- data/extra_docs/Index.md +33 -0
- data/extra_docs/MimeTypes.md +40 -0
- data/extra_docs/Models.md +272 -0
- data/extra_docs/Mongo.md +45 -0
- data/extra_docs/Processing.md +77 -0
- data/extra_docs/Rack.md +52 -0
- data/extra_docs/Rails2.md +57 -0
- data/extra_docs/Rails3.md +62 -0
- data/extra_docs/Sinatra.md +25 -0
- data/extra_docs/URLs.md +169 -0
- data/features/images.feature +47 -0
- data/features/no_processing.feature +14 -0
- data/features/rails_3.0.5.feature +8 -0
- data/features/steps/common_steps.rb +8 -0
- data/features/steps/dragonfly_steps.rb +66 -0
- data/features/steps/rails_steps.rb +28 -0
- data/features/support/env.rb +13 -0
- data/features/support/setup.rb +32 -0
- data/fixtures/rails_3.0.5/files/app/models/album.rb +7 -0
- data/fixtures/rails_3.0.5/files/app/views/albums/new.html.erb +7 -0
- data/fixtures/rails_3.0.5/files/app/views/albums/show.html.erb +6 -0
- data/fixtures/rails_3.0.5/files/config/initializers/dragonfly.rb +4 -0
- data/fixtures/rails_3.0.5/files/features/manage_album_images.feature +38 -0
- data/fixtures/rails_3.0.5/files/features/step_definitions/helper_steps.rb +7 -0
- data/fixtures/rails_3.0.5/files/features/step_definitions/image_steps.rb +25 -0
- data/fixtures/rails_3.0.5/files/features/support/paths.rb +17 -0
- data/fixtures/rails_3.0.5/files/features/text_images.feature +7 -0
- data/fixtures/rails_3.0.5/template.rb +20 -0
- data/irbrc.rb +18 -0
- data/lib/dragonfly.rb +55 -0
- data/lib/dragonfly/active_model_extensions.rb +13 -0
- data/lib/dragonfly/active_model_extensions/attachment.rb +250 -0
- data/lib/dragonfly/active_model_extensions/attachment_class_methods.rb +148 -0
- data/lib/dragonfly/active_model_extensions/class_methods.rb +95 -0
- data/lib/dragonfly/active_model_extensions/instance_methods.rb +28 -0
- data/lib/dragonfly/active_model_extensions/validations.rb +41 -0
- data/lib/dragonfly/analyser.rb +58 -0
- data/lib/dragonfly/analysis/file_command_analyser.rb +32 -0
- data/lib/dragonfly/analysis/image_magick_analyser.rb +6 -0
- data/lib/dragonfly/app.rb +172 -0
- data/lib/dragonfly/config/heroku.rb +19 -0
- data/lib/dragonfly/config/image_magick.rb +6 -0
- data/lib/dragonfly/config/rails.rb +20 -0
- data/lib/dragonfly/configurable.rb +207 -0
- data/lib/dragonfly/core_ext/array.rb +7 -0
- data/lib/dragonfly/core_ext/hash.rb +7 -0
- data/lib/dragonfly/core_ext/object.rb +12 -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/couch_data_store.rb +64 -0
- data/lib/dragonfly/data_storage/file_data_store.rb +141 -0
- data/lib/dragonfly/data_storage/mongo_data_store.rb +86 -0
- data/lib/dragonfly/data_storage/s3data_store.rb +145 -0
- data/lib/dragonfly/encoder.rb +13 -0
- data/lib/dragonfly/encoding/image_magick_encoder.rb +6 -0
- data/lib/dragonfly/function_manager.rb +71 -0
- data/lib/dragonfly/generation/image_magick_generator.rb +6 -0
- data/lib/dragonfly/generator.rb +9 -0
- data/lib/dragonfly/hash_with_css_style_keys.rb +21 -0
- data/lib/dragonfly/image_magick/analyser.rb +51 -0
- data/lib/dragonfly/image_magick/config.rb +41 -0
- data/lib/dragonfly/image_magick/encoder.rb +57 -0
- data/lib/dragonfly/image_magick/generator.rb +145 -0
- data/lib/dragonfly/image_magick/processor.rb +99 -0
- data/lib/dragonfly/image_magick/utils.rb +72 -0
- data/lib/dragonfly/image_magick_utils.rb +4 -0
- data/lib/dragonfly/job.rb +451 -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 +20 -0
- data/lib/dragonfly/processing/image_magick_processor.rb +6 -0
- data/lib/dragonfly/processor.rb +9 -0
- data/lib/dragonfly/rails/images.rb +27 -0
- data/lib/dragonfly/response.rb +97 -0
- data/lib/dragonfly/routed_endpoint.rb +40 -0
- data/lib/dragonfly/serializer.rb +32 -0
- data/lib/dragonfly/server.rb +113 -0
- data/lib/dragonfly/simple_cache.rb +23 -0
- data/lib/dragonfly/temp_object.rb +175 -0
- data/lib/dragonfly/url_mapper.rb +78 -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/dragonfly/active_model_extensions/model_spec.rb +1426 -0
- data/spec/dragonfly/active_model_extensions/spec_helper.rb +91 -0
- data/spec/dragonfly/analyser_spec.rb +123 -0
- data/spec/dragonfly/analysis/file_command_analyser_spec.rb +48 -0
- data/spec/dragonfly/app_spec.rb +135 -0
- data/spec/dragonfly/configurable_spec.rb +461 -0
- data/spec/dragonfly/core_ext/array_spec.rb +19 -0
- data/spec/dragonfly/core_ext/hash_spec.rb +19 -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/couch_data_store_spec.rb +76 -0
- data/spec/dragonfly/data_storage/file_data_store_spec.rb +296 -0
- data/spec/dragonfly/data_storage/mongo_data_store_spec.rb +57 -0
- data/spec/dragonfly/data_storage/s3_data_store_spec.rb +258 -0
- data/spec/dragonfly/data_storage/shared_data_store_examples.rb +77 -0
- data/spec/dragonfly/function_manager_spec.rb +154 -0
- data/spec/dragonfly/hash_with_css_style_keys_spec.rb +24 -0
- data/spec/dragonfly/image_magick/analyser_spec.rb +64 -0
- data/spec/dragonfly/image_magick/encoder_spec.rb +41 -0
- data/spec/dragonfly/image_magick/generator_spec.rb +172 -0
- data/spec/dragonfly/image_magick/processor_spec.rb +233 -0
- data/spec/dragonfly/image_magick/utils_spec.rb +18 -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 +173 -0
- data/spec/dragonfly/job_spec.rb +1046 -0
- data/spec/dragonfly/loggable_spec.rb +80 -0
- data/spec/dragonfly/middleware_spec.rb +47 -0
- data/spec/dragonfly/routed_endpoint_spec.rb +48 -0
- data/spec/dragonfly/serializer_spec.rb +61 -0
- data/spec/dragonfly/server_spec.rb +278 -0
- data/spec/dragonfly/simple_cache_spec.rb +27 -0
- data/spec/dragonfly/temp_object_spec.rb +306 -0
- data/spec/dragonfly/url_mapper_spec.rb +126 -0
- data/spec/functional/deprecations_spec.rb +51 -0
- data/spec/functional/image_magick_app_spec.rb +27 -0
- data/spec/functional/model_urls_spec.rb +85 -0
- data/spec/functional/remote_on_the_fly_spec.rb +51 -0
- data/spec/functional/to_response_spec.rb +31 -0
- data/spec/spec_helper.rb +51 -0
- data/spec/support/argument_matchers.rb +19 -0
- data/spec/support/image_matchers.rb +47 -0
- data/spec/support/simple_matchers.rb +53 -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 +89 -0
- data/yard/templates/default/module/html/configuration_summary.erb +31 -0
- data/yard/templates/default/module/setup.rb +17 -0
- metadata +544 -0
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
require 'couchrest'
|
|
2
|
+
|
|
3
|
+
module Dragonfly
|
|
4
|
+
module DataStorage
|
|
5
|
+
class CouchDataStore
|
|
6
|
+
|
|
7
|
+
include Configurable
|
|
8
|
+
include Serializer
|
|
9
|
+
|
|
10
|
+
configurable_attr :host, 'localhost'
|
|
11
|
+
configurable_attr :port, '5984'
|
|
12
|
+
configurable_attr :database, 'dragonfly'
|
|
13
|
+
configurable_attr :username
|
|
14
|
+
configurable_attr :password
|
|
15
|
+
|
|
16
|
+
def initialize(opts={})
|
|
17
|
+
self.host = opts[:host]
|
|
18
|
+
self.port = opts[:port]
|
|
19
|
+
self.database = opts[:database] if opts[:database]
|
|
20
|
+
self.username = opts[:username]
|
|
21
|
+
self.password = opts[:password]
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def store(temp_object, opts={})
|
|
25
|
+
meta = opts[:meta] || {}
|
|
26
|
+
name = meta[:name] || temp_object.original_filename || 'file'
|
|
27
|
+
content_type = opts[:content_type] || opts[:mime_type] || 'application/octet-stream'
|
|
28
|
+
|
|
29
|
+
temp_object.file do |f|
|
|
30
|
+
doc = CouchRest::Document.new(:meta => marshal_encode(meta))
|
|
31
|
+
response = db.save_doc(doc)
|
|
32
|
+
doc.put_attachment(name, f, {:content_type => content_type})
|
|
33
|
+
response['id']
|
|
34
|
+
end
|
|
35
|
+
rescue RuntimeError => e
|
|
36
|
+
raise UnableToStore, "#{e} - #{temp_object.inspect}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def retrieve(uid)
|
|
40
|
+
doc = db.get(uid)
|
|
41
|
+
name = doc['_attachments'].keys.first
|
|
42
|
+
[doc.fetch_attachment(name), marshal_decode(doc['meta'])]
|
|
43
|
+
rescue RestClient::ResourceNotFound => e
|
|
44
|
+
raise DataNotFound, "#{e} - #{uid}"
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def destroy(uid)
|
|
48
|
+
doc = db.get(uid)
|
|
49
|
+
db.delete_doc(doc)
|
|
50
|
+
rescue RestClient::ResourceNotFound => e
|
|
51
|
+
raise DataNotFound, "#{e} - #{uid}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def db
|
|
55
|
+
@db ||= begin
|
|
56
|
+
auth = username.blank? ? nil : "#{username}:#{password}@"
|
|
57
|
+
url = "http://#{auth}#{host}:#{port}"
|
|
58
|
+
CouchRest.new(url).database!(database)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
@@ -0,0 +1,141 @@
|
|
|
1
|
+
require 'pathname'
|
|
2
|
+
|
|
3
|
+
module Dragonfly
|
|
4
|
+
module DataStorage
|
|
5
|
+
|
|
6
|
+
class FileDataStore
|
|
7
|
+
|
|
8
|
+
# Exceptions
|
|
9
|
+
class UnableToFormUrl < RuntimeError; end
|
|
10
|
+
|
|
11
|
+
include Configurable
|
|
12
|
+
|
|
13
|
+
configurable_attr :root_path, '/var/tmp/dragonfly'
|
|
14
|
+
configurable_attr :server_root
|
|
15
|
+
configurable_attr :store_meta, true
|
|
16
|
+
|
|
17
|
+
def store(temp_object, opts={})
|
|
18
|
+
meta = opts[:meta] || {}
|
|
19
|
+
relative_path = if opts[:path]
|
|
20
|
+
opts[:path]
|
|
21
|
+
else
|
|
22
|
+
filename = meta[:name] || temp_object.original_filename || 'file'
|
|
23
|
+
relative_path = relative_path_for(filename)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
begin
|
|
27
|
+
path = absolute(relative_path)
|
|
28
|
+
until !File.exist?(path)
|
|
29
|
+
path = disambiguate(path)
|
|
30
|
+
end
|
|
31
|
+
prepare_path(path)
|
|
32
|
+
temp_object.to_file(path).close
|
|
33
|
+
store_meta_data(path, meta) if store_meta
|
|
34
|
+
rescue Errno::EACCES => e
|
|
35
|
+
raise UnableToStore, e.message
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
relative(path)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def retrieve(relative_path)
|
|
42
|
+
path = absolute(relative_path)
|
|
43
|
+
pathname = Pathname.new(path)
|
|
44
|
+
raise DataNotFound, "couldn't find file #{path}" unless pathname.exist?
|
|
45
|
+
[
|
|
46
|
+
pathname,
|
|
47
|
+
(store_meta ? retrieve_meta_data(path) : {})
|
|
48
|
+
]
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def destroy(relative_path)
|
|
52
|
+
path = absolute(relative_path)
|
|
53
|
+
FileUtils.rm path
|
|
54
|
+
FileUtils.rm_f meta_data_path(path)
|
|
55
|
+
FileUtils.rm_f deprecated_meta_data_path(path)
|
|
56
|
+
purge_empty_directories(relative_path)
|
|
57
|
+
rescue Errno::ENOENT => e
|
|
58
|
+
raise DataNotFound, e.message
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def url_for(relative_path, opts={})
|
|
62
|
+
if server_root.nil?
|
|
63
|
+
raise NotConfigured, "you need to configure server_root for #{self.class.name} in order to form urls"
|
|
64
|
+
else
|
|
65
|
+
_, __, path = absolute(relative_path).partition(server_root)
|
|
66
|
+
if path.empty?
|
|
67
|
+
raise UnableToFormUrl, "couldn't form url for uid #{relative_path.inspect} with root_path #{root_path.inspect} and server_root #{server_root.inspect}"
|
|
68
|
+
else
|
|
69
|
+
path
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
def disambiguate(path)
|
|
75
|
+
dirname = File.dirname(path)
|
|
76
|
+
basename = File.basename(path, '.*')
|
|
77
|
+
extname = File.extname(path)
|
|
78
|
+
"#{dirname}/#{basename}_#{(Time.now.usec*10 + rand(100)).to_s(32)}#{extname}"
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
private
|
|
82
|
+
|
|
83
|
+
def absolute(relative_path)
|
|
84
|
+
File.join(root_path, relative_path)
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
def relative(absolute_path)
|
|
88
|
+
absolute_path[/^#{Regexp.escape root_path}\/?(.*)$/, 1]
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def directory_empty?(path)
|
|
92
|
+
Dir.entries(path) == ['.','..']
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def meta_data_path(data_path)
|
|
96
|
+
"#{data_path}.meta"
|
|
97
|
+
end
|
|
98
|
+
|
|
99
|
+
def deprecated_meta_data_path(data_path)
|
|
100
|
+
"#{data_path}.extra"
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
def relative_path_for(filename)
|
|
104
|
+
time = Time.now
|
|
105
|
+
msec = time.usec / 1000
|
|
106
|
+
"#{time.strftime '%Y/%m/%d/%H_%M_%S'}_#{msec}_#{filename.gsub(/[^\w.]+/,'_')}"
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def store_meta_data(data_path, meta)
|
|
110
|
+
File.open(meta_data_path(data_path), 'wb') do |f|
|
|
111
|
+
f.write Marshal.dump(meta)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def retrieve_meta_data(data_path)
|
|
116
|
+
path = meta_data_path(data_path)
|
|
117
|
+
if File.exist?(path)
|
|
118
|
+
File.open(path,'rb'){|f| Marshal.load(f.read) }
|
|
119
|
+
else
|
|
120
|
+
deprecated_path = deprecated_meta_data_path(data_path)
|
|
121
|
+
File.exist?(deprecated_path) ? File.open(deprecated_path,'rb'){|f| Marshal.load(f.read) } : {}
|
|
122
|
+
end
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
def prepare_path(path)
|
|
126
|
+
dir = File.dirname(path)
|
|
127
|
+
FileUtils.mkdir_p(dir) unless File.exist?(dir)
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def purge_empty_directories(path)
|
|
131
|
+
containing_directory = Pathname.new(path).dirname
|
|
132
|
+
containing_directory.ascend do |relative_dir|
|
|
133
|
+
dir = absolute(relative_dir)
|
|
134
|
+
FileUtils.rmdir dir if directory_empty?(dir)
|
|
135
|
+
end
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
end
|
|
141
|
+
end
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
require 'mongo'
|
|
2
|
+
|
|
3
|
+
module Dragonfly
|
|
4
|
+
module DataStorage
|
|
5
|
+
class MongoDataStore
|
|
6
|
+
|
|
7
|
+
include Configurable
|
|
8
|
+
include Serializer
|
|
9
|
+
|
|
10
|
+
configurable_attr :host
|
|
11
|
+
configurable_attr :port
|
|
12
|
+
configurable_attr :database, 'dragonfly'
|
|
13
|
+
configurable_attr :username
|
|
14
|
+
configurable_attr :password
|
|
15
|
+
configurable_attr :connection
|
|
16
|
+
configurable_attr :db
|
|
17
|
+
|
|
18
|
+
# Mongo gem deprecated ObjectID in favour of ObjectId
|
|
19
|
+
OBJECT_ID = defined?(BSON::ObjectId) ? BSON::ObjectId : BSON::ObjectID
|
|
20
|
+
INVALID_OBJECT_ID = defined?(BSON::InvalidObjectId) ? BSON::InvalidObjectId : BSON::InvalidObjectID
|
|
21
|
+
|
|
22
|
+
def initialize(opts={})
|
|
23
|
+
self.host = opts[:host]
|
|
24
|
+
self.port = opts[:port]
|
|
25
|
+
self.database = opts[:database] if opts[:database]
|
|
26
|
+
self.username = opts[:username]
|
|
27
|
+
self.password = opts[:password]
|
|
28
|
+
self.connection = opts[:connection]
|
|
29
|
+
self.db = opts[:db]
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def store(temp_object, opts={})
|
|
33
|
+
ensure_authenticated!
|
|
34
|
+
temp_object.file do |f|
|
|
35
|
+
mongo_id = grid.put(f, :metadata => marshal_encode(opts[:meta] || {}))
|
|
36
|
+
mongo_id.to_s
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def retrieve(uid)
|
|
41
|
+
ensure_authenticated!
|
|
42
|
+
grid_io = grid.get(bson_id(uid))
|
|
43
|
+
meta = marshal_decode(grid_io.metadata)
|
|
44
|
+
meta.merge!(:stored_at => grid_io.upload_date)
|
|
45
|
+
[
|
|
46
|
+
grid_io.read,
|
|
47
|
+
meta
|
|
48
|
+
]
|
|
49
|
+
rescue Mongo::GridFileNotFound, INVALID_OBJECT_ID => e
|
|
50
|
+
raise DataNotFound, "#{e} - #{uid}"
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def destroy(uid)
|
|
54
|
+
ensure_authenticated!
|
|
55
|
+
grid.delete(bson_id(uid))
|
|
56
|
+
rescue Mongo::GridFileNotFound, INVALID_OBJECT_ID => e
|
|
57
|
+
raise DataNotFound, "#{e} - #{uid}"
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def connection
|
|
61
|
+
@connection ||= Mongo::Connection.new(host, port)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def db
|
|
65
|
+
@db ||= connection.db(database)
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def grid
|
|
69
|
+
@grid ||= Mongo::Grid.new(db)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def ensure_authenticated!
|
|
75
|
+
if username
|
|
76
|
+
@authenticated ||= db.authenticate(username, password)
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def bson_id(uid)
|
|
81
|
+
OBJECT_ID.from_string(uid)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
end
|
|
85
|
+
end
|
|
86
|
+
end
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
require 'fog'
|
|
2
|
+
|
|
3
|
+
module Dragonfly
|
|
4
|
+
module DataStorage
|
|
5
|
+
|
|
6
|
+
class S3DataStore
|
|
7
|
+
|
|
8
|
+
include Configurable
|
|
9
|
+
include Serializer
|
|
10
|
+
|
|
11
|
+
configurable_attr :bucket_name
|
|
12
|
+
configurable_attr :access_key_id
|
|
13
|
+
configurable_attr :secret_access_key
|
|
14
|
+
configurable_attr :use_filesystem, true
|
|
15
|
+
configurable_attr :region
|
|
16
|
+
configurable_attr :storage_headers, {'x-amz-acl' => 'public-read'}
|
|
17
|
+
configurable_attr :specific_uid
|
|
18
|
+
|
|
19
|
+
REGIONS = {
|
|
20
|
+
'us-east-1' => 's3.amazonaws.com', #default
|
|
21
|
+
'eu-west-1' => 's3-eu-west-1.amazonaws.com',
|
|
22
|
+
'ap-southeast-1' => 's3-ap-southeast-1.amazonaws.com',
|
|
23
|
+
'us-west-1' => 's3-us-west-1.amazonaws.com'
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
def initialize(opts={})
|
|
27
|
+
self.bucket_name = opts[:bucket_name]
|
|
28
|
+
self.access_key_id = opts[:access_key_id]
|
|
29
|
+
self.secret_access_key = opts[:secret_access_key]
|
|
30
|
+
self.region = opts[:region]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def store(temp_object, opts={})
|
|
34
|
+
ensure_configured
|
|
35
|
+
ensure_bucket_initialized
|
|
36
|
+
|
|
37
|
+
meta = opts[:meta] || {}
|
|
38
|
+
headers = opts[:headers] || {}
|
|
39
|
+
uid = opts[:path] || generate_uid(meta[:name] || temp_object.original_filename || 'file')
|
|
40
|
+
|
|
41
|
+
if use_filesystem
|
|
42
|
+
temp_object.file do |f|
|
|
43
|
+
storage.put_object(bucket_name, uid, f, full_storage_headers(headers, meta))
|
|
44
|
+
end
|
|
45
|
+
else
|
|
46
|
+
storage.put_object(bucket_name, uid, temp_object.data, full_storage_headers(headers, meta))
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
uid
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def retrieve(uid)
|
|
53
|
+
ensure_configured
|
|
54
|
+
response = storage.get_object(bucket_name, uid)
|
|
55
|
+
[
|
|
56
|
+
response.body,
|
|
57
|
+
parse_s3_metadata(response.headers)
|
|
58
|
+
]
|
|
59
|
+
rescue Excon::Errors::NotFound => e
|
|
60
|
+
raise DataNotFound, "#{e} - #{uid}"
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def destroy(uid)
|
|
64
|
+
storage.delete_object(bucket_name, uid)
|
|
65
|
+
rescue Excon::Errors::NotFound => e
|
|
66
|
+
raise DataNotFound, "#{e} - #{uid}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def url_for(uid, opts={})
|
|
70
|
+
if opts && opts[:expires]
|
|
71
|
+
storage.get_object_url(bucket_name, uid, opts[:expires])
|
|
72
|
+
else
|
|
73
|
+
"http://#{bucket_name}.s3.amazonaws.com/#{uid}"
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def domain
|
|
78
|
+
REGIONS[get_region]
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def storage
|
|
82
|
+
@storage ||= Fog::Storage.new(
|
|
83
|
+
:provider => 'AWS',
|
|
84
|
+
:aws_access_key_id => access_key_id,
|
|
85
|
+
:aws_secret_access_key => secret_access_key,
|
|
86
|
+
:region => region
|
|
87
|
+
)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def bucket_exists?
|
|
91
|
+
storage.get_bucket_location(bucket_name)
|
|
92
|
+
true
|
|
93
|
+
rescue Excon::Errors::NotFound => e
|
|
94
|
+
false
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
private
|
|
98
|
+
|
|
99
|
+
def ensure_configured
|
|
100
|
+
unless @configured
|
|
101
|
+
[:bucket_name, :access_key_id, :secret_access_key].each do |attr|
|
|
102
|
+
raise NotConfigured, "You need to configure #{self.class.name} with #{attr}" if send(attr).nil?
|
|
103
|
+
end
|
|
104
|
+
@configured = true
|
|
105
|
+
end
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def ensure_bucket_initialized
|
|
109
|
+
unless @bucket_initialized
|
|
110
|
+
storage.put_bucket(bucket_name, 'LocationConstraint' => region) unless bucket_exists?
|
|
111
|
+
@bucket_initialized = true
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def get_region
|
|
116
|
+
reg = region || 'us-east-1'
|
|
117
|
+
raise "Invalid region #{reg} - should be one of #{valid_regions.join(', ')}" unless valid_regions.include?(reg)
|
|
118
|
+
reg
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def generate_uid(name)
|
|
122
|
+
if self.specific_uid.is_a?(Proc)
|
|
123
|
+
self.specific_uid.call(name)
|
|
124
|
+
else
|
|
125
|
+
"#{Time.now.strftime '%Y/%m/%d/%H/%M/%S'}/#{rand(1000)}/#{name.gsub(/[^\w.]+/, '_')}"
|
|
126
|
+
end
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def full_storage_headers(headers, meta)
|
|
130
|
+
{'x-amz-meta-extra' => marshal_encode(meta)}.merge(storage_headers).merge(headers)
|
|
131
|
+
end
|
|
132
|
+
|
|
133
|
+
def parse_s3_metadata(headers)
|
|
134
|
+
encoded_meta = headers['x-amz-meta-extra']
|
|
135
|
+
(marshal_decode(encoded_meta) if encoded_meta) || {}
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def valid_regions
|
|
139
|
+
REGIONS.keys
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
end
|
|
145
|
+
end
|