card-mod-carrierwave 0.11.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/config/core_initializers/carrierwave.rb +5 -0
- data/lib/carrier_wave/card_mount.rb +134 -0
- data/lib/carrier_wave/file_card_uploader.rb +320 -0
- data/lib/carrier_wave/image_card_uploader.rb +59 -0
- data/set/abstract/attachment.rb +112 -0
- data/set/abstract/attachment/cloud.rb +133 -0
- data/set/abstract/attachment/coded.rb +22 -0
- data/set/abstract/attachment/local.rb +40 -0
- data/set/abstract/attachment/paths.rb +58 -0
- data/set/abstract/attachment/storage_type.rb +170 -0
- data/set/abstract/attachment/upload_cache.rb +85 -0
- data/set/abstract/attachment/web.rb +3 -0
- data/set/all/file_utils.rb +41 -0
- data/set/self/admin.rb +23 -0
- data/set/self/favicon.rb +16 -0
- data/set/self/new_file.rb +13 -0
- data/set/self/new_image.rb +13 -0
- data/set/type/file.rb +116 -0
- data/set/type/file/file_chooser.haml +15 -0
- data/set/type/file/preview_editor.haml +19 -0
- data/set/type/image.rb +101 -0
- data/set/type/image/html_views.rb +69 -0
- metadata +138 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: 29840e46e626a7121a84faa9b4b0f9889694ab7653031bd9cf97ccb896d46263
|
4
|
+
data.tar.gz: 503d37c6471c6fd8d0e6cba3c94c65097663ab0854fc24861673c80f913e1e86
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 65f55c9a919ea38e364f5863030d35b5674569dab60454ee157c3e78c762afbc427b3e258246d8389714e00eb752bf8076b948e8a5c70b4d202a2dd63ff01695
|
7
|
+
data.tar.gz: 69c7e3184ca375b4342ad3a4f5dd2ae9f8654666e0e6cefded79f5b3c2823dfd8a074ad906acdb28b749f51413c49ebba315c7a790aa17907b1da063baf07791
|
@@ -0,0 +1,134 @@
|
|
1
|
+
require "carrierwave"
|
2
|
+
|
3
|
+
module CarrierWave
|
4
|
+
# adapt carrierwave mount to cards
|
5
|
+
module CardMount
|
6
|
+
include CarrierWave::Mount
|
7
|
+
|
8
|
+
def uploaders
|
9
|
+
Card.uploaders ||= {}
|
10
|
+
end
|
11
|
+
|
12
|
+
def uploader_options
|
13
|
+
Card.uploader_options ||= {}
|
14
|
+
end
|
15
|
+
|
16
|
+
def mount_uploader column, uploader=nil, options={}, &block
|
17
|
+
options[:mount_on] ||= :db_content
|
18
|
+
super
|
19
|
+
|
20
|
+
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
21
|
+
event :store_#{column}_event, :finalize,
|
22
|
+
on: :save, when: :store_#{column}_event? do
|
23
|
+
store_#{column}!
|
24
|
+
end
|
25
|
+
|
26
|
+
# remove files only if card has no history
|
27
|
+
event :remove_#{column}_event, :finalize,
|
28
|
+
on: :delete, when: proc { |c| !c.history? } do
|
29
|
+
remove_#{column}!
|
30
|
+
end
|
31
|
+
event :mark_remove_#{column}_false_event, :finalize,
|
32
|
+
on: :update do
|
33
|
+
mark_remove_#{column}_false
|
34
|
+
end
|
35
|
+
event :store_previous_changes_for_#{column}_event, :store,
|
36
|
+
on: :update, when: proc { |c| !c.history? } do
|
37
|
+
store_previous_changes_for_#{column}
|
38
|
+
end
|
39
|
+
event :remove_previously_stored_#{column}_event, :finalize,
|
40
|
+
on: :update, when: proc { |c| !c.history?} do
|
41
|
+
remove_previously_stored_#{column}
|
42
|
+
end
|
43
|
+
|
44
|
+
# don't attempt to store coded images unless ENV specifies it
|
45
|
+
def store_#{column}_event?
|
46
|
+
!coded? || ENV["STORE_CODED_FILES"]
|
47
|
+
end
|
48
|
+
|
49
|
+
def attachment
|
50
|
+
#{column}
|
51
|
+
end
|
52
|
+
|
53
|
+
def store_attachment!
|
54
|
+
store_#{column}!
|
55
|
+
end
|
56
|
+
|
57
|
+
def attachment_name
|
58
|
+
"#{column}".to_sym
|
59
|
+
end
|
60
|
+
|
61
|
+
def read_uploader *args
|
62
|
+
read_attribute *args
|
63
|
+
end
|
64
|
+
|
65
|
+
def write_uploader *args
|
66
|
+
write_attribute *args
|
67
|
+
end
|
68
|
+
|
69
|
+
def #{column}=(new_file)
|
70
|
+
return if new_file.blank?
|
71
|
+
assign_file(new_file) { super }
|
72
|
+
end
|
73
|
+
|
74
|
+
def remote_#{column}_url=(url)
|
75
|
+
assign_file(url) { super }
|
76
|
+
end
|
77
|
+
|
78
|
+
def assign_file file
|
79
|
+
db_column = _mounter(:#{column}).serialization_column
|
80
|
+
send(:"\#{db_column}_will_change!") # unless attribute_is_changing? db_column
|
81
|
+
if web?
|
82
|
+
self.content = file
|
83
|
+
else
|
84
|
+
send(:"#{column}_will_change!")
|
85
|
+
yield
|
86
|
+
end
|
87
|
+
end
|
88
|
+
|
89
|
+
def remove_#{column}=(value)
|
90
|
+
column = _mounter(:#{column}).serialization_column
|
91
|
+
send(:"\#{column}_will_change!")
|
92
|
+
super
|
93
|
+
end
|
94
|
+
|
95
|
+
def remove_#{column}!
|
96
|
+
self.remove_#{column} = true
|
97
|
+
write_#{column}_identifier
|
98
|
+
self.remove_#{column} = false
|
99
|
+
super
|
100
|
+
end
|
101
|
+
|
102
|
+
def #{column}_will_change!
|
103
|
+
@#{column}_changed = true
|
104
|
+
@#{column}_is_changing = true
|
105
|
+
end
|
106
|
+
|
107
|
+
def #{column}_is_changing?
|
108
|
+
@#{column}_is_changing
|
109
|
+
end
|
110
|
+
|
111
|
+
def #{column}_changed?
|
112
|
+
@#{column}_changed
|
113
|
+
end
|
114
|
+
|
115
|
+
def serializable_hash(options=nil)
|
116
|
+
hash = {}
|
117
|
+
|
118
|
+
except = options && options[:except] &&
|
119
|
+
Array.wrap(options[:except]).map(&:to_s)
|
120
|
+
only = options && options[:only] &&
|
121
|
+
Array.wrap(options[:only]).map(&:to_s)
|
122
|
+
|
123
|
+
self.class.uploaders.each do |column, uploader|
|
124
|
+
if (!only && !except) || (only && only.include?(column.to_s)) ||
|
125
|
+
(!only && except && !except.include?(column.to_s))
|
126
|
+
hash[column.to_s] = _mounter(column).uploader.serializable_hash
|
127
|
+
end
|
128
|
+
end
|
129
|
+
super(options).merge(hash)
|
130
|
+
end
|
131
|
+
RUBY
|
132
|
+
end
|
133
|
+
end
|
134
|
+
end
|
@@ -0,0 +1,320 @@
|
|
1
|
+
module CarrierWave
|
2
|
+
class << self
|
3
|
+
def tmp_path
|
4
|
+
@tmp_path ||= Card.paths["tmp"].existent.first
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class SanitizedFile
|
9
|
+
def content_type
|
10
|
+
# the original content_type method doesn't seem to be very reliable
|
11
|
+
# It uses mime_magic_content_type - which returns invalid/invalid for css files
|
12
|
+
# that start with a comment - as the second option. (we switch the order and
|
13
|
+
# use it as the third option)
|
14
|
+
@content_type ||=
|
15
|
+
existing_content_type ||
|
16
|
+
mini_mime_content_type ||
|
17
|
+
mime_magic_content_type
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
module Uploader
|
22
|
+
# Implements a different name pattern for versions than CarrierWave's
|
23
|
+
# default: we expect the version name at the end of the filename separated
|
24
|
+
# by a dash
|
25
|
+
module Versions
|
26
|
+
private
|
27
|
+
|
28
|
+
# put version at the end of the filename
|
29
|
+
def full_filename for_file
|
30
|
+
name = super(for_file)
|
31
|
+
parts = name.split "."
|
32
|
+
basename = [parts.shift, version_name].compact.join("-")
|
33
|
+
"#{basename}.#{parts.join('.')}"
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# Takes care of the file upload for cards with attached files.
|
39
|
+
# Most of the upload behaviour depends on the card itself.
|
40
|
+
# (e.g. card type and storage option chosen for the card). So in contrary
|
41
|
+
# to CarrierWave's default uploader we depend very much on the model
|
42
|
+
# (= card object) to get the correct paths for retrieving and storing
|
43
|
+
# the file.
|
44
|
+
#
|
45
|
+
# Cards that support attachments (by default those are cards of type "file"
|
46
|
+
# and "image") accept a file handle as a card attribute.
|
47
|
+
#
|
48
|
+
# @example Attaching a file to a file card
|
49
|
+
# Card.create name: "file card", type: :file,
|
50
|
+
# file: File.new(path_to_file)
|
51
|
+
#
|
52
|
+
# @example Attaching a image to a image card
|
53
|
+
# Card.create name: "file card", type: :image,
|
54
|
+
# image: File.new(path_to_image)
|
55
|
+
#
|
56
|
+
# It's possible to upload files using a url. The card attribute for that is
|
57
|
+
# remote_<attachment_type>_url
|
58
|
+
#
|
59
|
+
# @example Create a file card using a remote url
|
60
|
+
# Card.create name: "file_card", type: :file,
|
61
|
+
# remote_file_url: "http://a.file.in/the.web"
|
62
|
+
#
|
63
|
+
# @example Updating a image card using a remote url
|
64
|
+
# card.update remote_image_url: "http://a.image/somewhere.png"
|
65
|
+
#
|
66
|
+
# ## Storage types
|
67
|
+
# You can choose between four different storage options
|
68
|
+
# - coded: These files are in the codebase, like the default logo.
|
69
|
+
# Every view is a decko request.
|
70
|
+
# - local: Uploaded files which are stored in a local upload directory
|
71
|
+
# (upload path is configurable via config.paths["files"]).
|
72
|
+
# If read permissions are set such that "Anyone" can read, then there is
|
73
|
+
# a symlink from the public directory. Otherwise every view is a decko
|
74
|
+
# request.
|
75
|
+
# - cloud: You can configure buckets that refer to an external storage
|
76
|
+
# service. Link is rendered as absolute url
|
77
|
+
# - web: A fixed url (to external source). No upload or other file
|
78
|
+
# processing. Link is just the saved url.
|
79
|
+
#
|
80
|
+
# Currently, there is no web interface that let's a user or administrator
|
81
|
+
# choose a storage option for a specific card or set of cards.
|
82
|
+
# There is only a global config option to set the storage type for all new
|
83
|
+
# uploads (config.storage_type). On the *admin card it's possible to
|
84
|
+
# update all existing file cards according to the current global config.
|
85
|
+
#
|
86
|
+
# Storage types for single cards can be changed by developers using
|
87
|
+
# the card attributes "storage_type", "bucket", and "mod".
|
88
|
+
#
|
89
|
+
# @example Creating a hard-coded file
|
90
|
+
# Card.create name: "file card", type_id: Card::FileID,
|
91
|
+
# file: File.new(path),
|
92
|
+
# storage_type: :coded, mod: "account"
|
93
|
+
#
|
94
|
+
# @example Moving a file to a cloud service
|
95
|
+
# # my_deck/config/application.rb:
|
96
|
+
# config.file_buckets = {
|
97
|
+
# aws_bucket: {
|
98
|
+
# provider: "fog/aws",
|
99
|
+
# directory: "bucket-name",
|
100
|
+
# subdirectory: "files",
|
101
|
+
# credentials: {
|
102
|
+
# provider: 'AWS' # required
|
103
|
+
# aws_access_key_id: 'key' # required
|
104
|
+
# aws_secret_access_key: 'secret-key' # required
|
105
|
+
# public: true,
|
106
|
+
# }
|
107
|
+
# }
|
108
|
+
#
|
109
|
+
# # decko console or rake task:
|
110
|
+
# card.update storage_type: :cloud, bucket: :aws_bucket
|
111
|
+
#
|
112
|
+
# @example Creating a file card with fixed external link
|
113
|
+
# Card.create name: "file card", type_id: Card::FileID,
|
114
|
+
# content: "http://animals.org/cat.png"
|
115
|
+
# storage_type: :web
|
116
|
+
#
|
117
|
+
# Card.create name: "file card", type_id: Card::FileID,
|
118
|
+
# file: "http://animals.org/cat.png"
|
119
|
+
# storage_type: :web
|
120
|
+
#
|
121
|
+
# Depending on the storage type the uploader uses the following paths
|
122
|
+
# and identifiers.
|
123
|
+
# ### Identifier (stored in the database as db_content)
|
124
|
+
# - coded: :codename/mod_name.ext
|
125
|
+
# - local: ~card_id/action_id.ext
|
126
|
+
# - cloud: (bucket)/card_id/action_id.ext
|
127
|
+
# - web: http://url
|
128
|
+
#
|
129
|
+
# ### Storage path
|
130
|
+
# - coded:
|
131
|
+
# mod_dir/file/codename/type_code(-variant).ext (no colon on codename!)
|
132
|
+
# - local:
|
133
|
+
# files_dir/card_id/action_id(-variant).ext (no tilde on id!)
|
134
|
+
# - cloud:
|
135
|
+
# bucket/bucket_subdir/id/action_id(-variant).ext
|
136
|
+
# - web: no storage
|
137
|
+
#
|
138
|
+
# Variants are only used for images. Possible options are
|
139
|
+
# icon|small|medium|large|original.
|
140
|
+
# files_dir, bucket, and bucket_subdir can be changed via config options.
|
141
|
+
#
|
142
|
+
# ### Supported url patterns
|
143
|
+
# mark.ext
|
144
|
+
# mark/revision.ext
|
145
|
+
# mark/revision-variant.ext
|
146
|
+
# /files/mark/revision-variant.ext # <- public symlink if readable by
|
147
|
+
# # "Anyone"
|
148
|
+
#
|
149
|
+
# <mark> can be one of the following options
|
150
|
+
# - <card name>
|
151
|
+
# - ~<card id>
|
152
|
+
# - :<code name>
|
153
|
+
#
|
154
|
+
# <revision> is the mod name if the file is coded or and action_id in any
|
155
|
+
# case
|
156
|
+
#
|
157
|
+
# Examples:
|
158
|
+
# *logo.png
|
159
|
+
# ~22/33-medium.png # local
|
160
|
+
# :yeti_skin/standard-large.png # coded
|
161
|
+
#
|
162
|
+
class FileCardUploader < Uploader::Base
|
163
|
+
attr_accessor :mod
|
164
|
+
include Card::Env::Location
|
165
|
+
|
166
|
+
STORAGE_TYPES = [:cloud, :web, :coded, :local].freeze
|
167
|
+
CONFIG_OPTIONS = [:provider, :attributes, :directory, :public, :credentials,
|
168
|
+
:authenticated_url_expiration, :use_ssl_for_aws].freeze
|
169
|
+
CONFIG_CREDENTIAL_OPTIONS = [
|
170
|
+
:provider,
|
171
|
+
:aws_access_key_id, :aws_secret_access_key, :region, :host, :endpoint,
|
172
|
+
:google_access_key_id, :google_secret_access_key
|
173
|
+
].freeze
|
174
|
+
delegate :store_dir, :retrieve_dir, :file_dir, :mod, :bucket, to: :model
|
175
|
+
|
176
|
+
def valid?
|
177
|
+
extension.present?
|
178
|
+
end
|
179
|
+
|
180
|
+
def filename
|
181
|
+
if model.coded?
|
182
|
+
"#{model.type_code}#{extension}"
|
183
|
+
else
|
184
|
+
"#{action_id}#{extension}"
|
185
|
+
end
|
186
|
+
end
|
187
|
+
|
188
|
+
def extension
|
189
|
+
case
|
190
|
+
when file&.extension.present? then ".#{file.extension}"
|
191
|
+
when card_content = model.content then File.extname(card_content)
|
192
|
+
when orig = original_filename then File.extname(orig)
|
193
|
+
else ""
|
194
|
+
end.downcase
|
195
|
+
end
|
196
|
+
|
197
|
+
# generate identifier that gets stored in the card's db_content field
|
198
|
+
# @param opts [Hash] generate an identifier using the given storage options
|
199
|
+
# instead of the storage options derived from the model and
|
200
|
+
# the global configuration
|
201
|
+
# @option opts [Symbol] storage_type
|
202
|
+
# @option opts [String] mod
|
203
|
+
# @option opts [Symbol] bucket
|
204
|
+
def db_content opts={}
|
205
|
+
model.with_storage_options opts do
|
206
|
+
return model.content if model.web?
|
207
|
+
return "" unless file.present?
|
208
|
+
"%s/%s" % [file_dir, url_filename]
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
def url_filename opts={}
|
213
|
+
model.with_storage_options opts do
|
214
|
+
if model.coded?
|
215
|
+
"#{model.mod}#{extension}"
|
216
|
+
else
|
217
|
+
"#{action_id}#{extension}"
|
218
|
+
end
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
222
|
+
# @option opts [Symbol] :absolute - return absolute url
|
223
|
+
def url opts={}
|
224
|
+
if model.cloud?
|
225
|
+
file&.url
|
226
|
+
elsif model.web?
|
227
|
+
model.content
|
228
|
+
else
|
229
|
+
local_url opts
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def local_url opts={}
|
234
|
+
"%s/%s/%s" % [local_url_base(opts), file_dir, full_filename(url_filename(opts))]
|
235
|
+
end
|
236
|
+
|
237
|
+
def local_url_base opts={}
|
238
|
+
web_path = Card.config.files_web_path
|
239
|
+
opts.delete(:absolute) ? card_url(web_path) : card_path(web_path)
|
240
|
+
end
|
241
|
+
|
242
|
+
def public_path
|
243
|
+
File.join Cardio.paths["public"].existent.first, url
|
244
|
+
end
|
245
|
+
|
246
|
+
def cache_dir
|
247
|
+
@model.files_base_dir + "/cache"
|
248
|
+
end
|
249
|
+
|
250
|
+
# Carrierwave calls store_path without argument when it stores the file
|
251
|
+
# and with the identifier from the db when it retrieves the file.
|
252
|
+
# In our case the first part of our identifier is not part of the path
|
253
|
+
# but we can construct the filename from db data. So we don't need the
|
254
|
+
# identifier.
|
255
|
+
def store_path for_file=nil
|
256
|
+
if for_file
|
257
|
+
retrieve_path
|
258
|
+
else
|
259
|
+
File.join([store_dir, full_filename(filename)].compact)
|
260
|
+
end
|
261
|
+
end
|
262
|
+
|
263
|
+
def retrieve_path
|
264
|
+
File.join([retrieve_dir, full_filename(filename)].compact)
|
265
|
+
end
|
266
|
+
|
267
|
+
def tmp_path
|
268
|
+
Dir.mkdir model.tmp_upload_dir unless Dir.exist? model.tmp_upload_dir
|
269
|
+
File.join model.tmp_upload_dir, filename
|
270
|
+
end
|
271
|
+
|
272
|
+
def create_versions? new_file
|
273
|
+
model.create_versions? new_file
|
274
|
+
end
|
275
|
+
|
276
|
+
# paperclip compatibility used in type/file.rb#core (base format)
|
277
|
+
def path version=nil
|
278
|
+
version ? versions[version].path : super()
|
279
|
+
end
|
280
|
+
|
281
|
+
def original_filename
|
282
|
+
@original_filename ||= model.selected_action &&
|
283
|
+
model.selected_action.comment
|
284
|
+
end
|
285
|
+
|
286
|
+
def action_id
|
287
|
+
model.selected_content_action_id || action_id_stand_in
|
288
|
+
end
|
289
|
+
|
290
|
+
# delegate carrierwave's fog config methods to bucket configuration
|
291
|
+
::CarrierWave::FileCardUploader::CONFIG_OPTIONS.each do |name|
|
292
|
+
define_method("fog_#{name}") { bucket_config name }
|
293
|
+
end
|
294
|
+
|
295
|
+
def bucket_config option
|
296
|
+
@model.bucket_config[option]
|
297
|
+
end
|
298
|
+
|
299
|
+
def asset_host
|
300
|
+
bucket_config(:asset_host) || super
|
301
|
+
end
|
302
|
+
|
303
|
+
private
|
304
|
+
|
305
|
+
# used as action_id in the filename
|
306
|
+
# if card is not #actionable?
|
307
|
+
def action_id_stand_in
|
308
|
+
Time.now.to_i
|
309
|
+
end
|
310
|
+
|
311
|
+
def storage
|
312
|
+
case @model.storage_type
|
313
|
+
when :cloud
|
314
|
+
::CarrierWave::Storage::Fog.new(self)
|
315
|
+
else
|
316
|
+
::CarrierWave::Storage::File.new(self)
|
317
|
+
end
|
318
|
+
end
|
319
|
+
end
|
320
|
+
end
|