mongoid-grid_fs 2.3.0 → 2.5.0
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.
- checksums.yaml +5 -5
- data/README.md +3 -7
- data/Rakefile +10 -7
- data/lib/mongoid/grid_fs/version.rb +1 -1
- data/lib/mongoid/grid_fs.rb +418 -430
- data/lib/mongoid-grid_fs.rb +1 -1
- data/test/helper.rb +1 -5
- data/test/mongoid-grid_fs_test.rb +125 -136
- data/test/testing.rb +65 -66
- metadata +72 -23
data/lib/mongoid/grid_fs.rb
CHANGED
@@ -1,585 +1,573 @@
|
|
1
|
-
require
|
2
|
-
require
|
1
|
+
require 'mongoid'
|
2
|
+
require 'mime/types'
|
3
3
|
|
4
4
|
##
|
5
5
|
#
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
def self.#{
|
36
|
-
::Mongoid::GridFs::Fs::#{
|
6
|
+
module Mongoid
|
7
|
+
class GridFs
|
8
|
+
class << GridFs
|
9
|
+
attr_accessor :namespace
|
10
|
+
attr_accessor :file_model
|
11
|
+
attr_accessor :chunk_model
|
12
|
+
|
13
|
+
def init!
|
14
|
+
GridFs.build_namespace_for(:Fs)
|
15
|
+
|
16
|
+
GridFs.namespace = Fs
|
17
|
+
GridFs.file_model = Fs.file_model
|
18
|
+
GridFs.chunk_model = Fs.chunk_model
|
19
|
+
|
20
|
+
const_set(:File, Fs.file_model)
|
21
|
+
const_set(:Chunk, Fs.chunk_model)
|
22
|
+
|
23
|
+
to_delegate = %w(
|
24
|
+
put
|
25
|
+
get
|
26
|
+
delete
|
27
|
+
find
|
28
|
+
[]
|
29
|
+
[]=
|
30
|
+
clear
|
31
|
+
)
|
32
|
+
|
33
|
+
to_delegate.each do |method|
|
34
|
+
class_eval <<-__
|
35
|
+
def self.#{method}(*args, &block)
|
36
|
+
::Mongoid::GridFs::Fs::#{method}(*args, &block)
|
37
37
|
end
|
38
38
|
__
|
39
|
-
end
|
40
39
|
end
|
41
40
|
end
|
41
|
+
end
|
42
42
|
|
43
43
|
##
|
44
44
|
#
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
45
|
+
def self.namespace_for(prefix)
|
46
|
+
prefix = prefix.to_s.downcase
|
47
|
+
const = "::GridFs::#{prefix.to_s.camelize}"
|
48
|
+
namespace = const.split(/::/).last
|
49
|
+
const_defined?(namespace) ? const_get(namespace) : build_namespace_for(namespace)
|
50
|
+
end
|
51
51
|
|
52
52
|
##
|
53
53
|
#
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
54
|
+
def self.build_namespace_for(prefix)
|
55
|
+
prefix = prefix.to_s.downcase
|
56
|
+
const = prefix.camelize
|
57
|
+
|
58
|
+
namespace =
|
59
|
+
Module.new do
|
60
|
+
module_eval(&NamespaceMixin)
|
61
|
+
self
|
62
|
+
end
|
63
63
|
|
64
|
-
|
64
|
+
const_set(const, namespace)
|
65
65
|
|
66
|
-
|
67
|
-
|
66
|
+
file_model = build_file_model_for(namespace)
|
67
|
+
chunk_model = build_chunk_model_for(namespace)
|
68
68
|
|
69
|
-
|
70
|
-
|
69
|
+
file_model.namespace = namespace
|
70
|
+
chunk_model.namespace = namespace
|
71
71
|
|
72
|
-
|
73
|
-
|
72
|
+
file_model.chunk_model = chunk_model
|
73
|
+
chunk_model.file_model = file_model
|
74
74
|
|
75
|
-
|
76
|
-
|
77
|
-
|
75
|
+
namespace.prefix = prefix
|
76
|
+
namespace.file_model = file_model
|
77
|
+
namespace.chunk_model = chunk_model
|
78
78
|
|
79
|
-
|
80
|
-
|
79
|
+
namespace.send(:const_set, :File, file_model)
|
80
|
+
namespace.send(:const_set, :Chunk, chunk_model)
|
81
81
|
|
82
|
-
|
83
|
-
|
82
|
+
# at_exit{ file_model.create_indexes rescue nil }
|
83
|
+
# at_exit{ chunk_model.create_indexes rescue nil }
|
84
84
|
|
85
|
-
|
86
|
-
|
85
|
+
const_get(const)
|
86
|
+
end
|
87
87
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
88
|
+
NamespaceMixin = proc do
|
89
|
+
class << self
|
90
|
+
attr_accessor :prefix
|
91
|
+
attr_accessor :file_model
|
92
|
+
attr_accessor :chunk_model
|
93
93
|
|
94
|
-
|
95
|
-
|
96
|
-
|
94
|
+
def to_s
|
95
|
+
prefix
|
96
|
+
end
|
97
97
|
|
98
|
-
|
99
|
-
|
100
|
-
|
98
|
+
def namespace
|
99
|
+
prefix
|
100
|
+
end
|
101
101
|
|
102
|
-
|
103
|
-
|
104
|
-
|
102
|
+
def put(readable, attributes = {})
|
103
|
+
file = file_model.create
|
104
|
+
attributes.to_options!
|
105
105
|
|
106
|
-
|
107
|
-
file.id = attributes.delete(:id)
|
108
|
-
end
|
106
|
+
file.id = attributes.delete(:id) if attributes.key?(:id)
|
109
107
|
|
110
|
-
|
111
|
-
file.id = attributes.delete(:_id)
|
112
|
-
end
|
108
|
+
file.id = attributes.delete(:_id) if attributes.key?(:_id)
|
113
109
|
|
114
|
-
|
115
|
-
attributes[:contentType] = attributes.delete(:content_type)
|
116
|
-
end
|
110
|
+
attributes[:contentType] = attributes.delete(:content_type) if attributes.key?(:content_type)
|
117
111
|
|
118
|
-
|
119
|
-
attributes[:uploadDate] = attributes.delete(:upload_date)
|
120
|
-
end
|
112
|
+
attributes[:uploadDate] = attributes.delete(:upload_date) if attributes.key?(:upload_date)
|
121
113
|
|
122
|
-
|
123
|
-
attributes[:metadata] = attributes.delete(:meta_data)
|
124
|
-
end
|
114
|
+
attributes[:metadata] = attributes.delete(:meta_data) if attributes.key?(:meta_data)
|
125
115
|
|
126
|
-
|
127
|
-
attributes[:aliases] = Array(attributes.delete(:aliases)).flatten.compact.map{|a| "#{ a }"}
|
128
|
-
end
|
116
|
+
attributes[:aliases] = Array(attributes.delete(:aliases)).flatten.compact.map(&:to_s) if attributes.key?(:aliases)
|
129
117
|
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
118
|
+
md5 = Digest::MD5.new
|
119
|
+
length = 0
|
120
|
+
chunkSize = file.chunkSize
|
121
|
+
n = 0
|
134
122
|
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
123
|
+
GridFs.reading(readable) do |io|
|
124
|
+
unless attributes.key?(:filename)
|
125
|
+
attributes[:filename] =
|
126
|
+
[file.id.to_s, GridFs.extract_basename(io)].join('/').squeeze('/')
|
127
|
+
end
|
140
128
|
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
129
|
+
unless attributes.key?(:contentType)
|
130
|
+
attributes[:contentType] =
|
131
|
+
GridFs.extract_content_type(attributes[:filename]) || file.contentType
|
132
|
+
end
|
145
133
|
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
end
|
134
|
+
GridFs.chunking(io, chunkSize) do |buf|
|
135
|
+
md5 << buf
|
136
|
+
length += buf.size
|
137
|
+
file.chunks.create(data: binary_for(buf), n: n)
|
138
|
+
n += 1
|
152
139
|
end
|
140
|
+
end
|
153
141
|
|
154
|
-
|
155
|
-
|
156
|
-
|
142
|
+
attributes[:length] ||= length
|
143
|
+
attributes[:uploadDate] ||= Time.now.utc
|
144
|
+
attributes[:md5] ||= md5.hexdigest
|
157
145
|
|
158
|
-
|
146
|
+
file.update_attributes(attributes)
|
159
147
|
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
148
|
+
file
|
149
|
+
rescue StandardError
|
150
|
+
file.destroy
|
151
|
+
raise
|
152
|
+
end
|
165
153
|
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
end
|
154
|
+
def binary_for(*buf)
|
155
|
+
if defined?(Moped::BSON)
|
156
|
+
Moped::BSON::Binary.new(:generic, buf.join)
|
157
|
+
else
|
158
|
+
BSON::Binary.new(buf.join, :generic)
|
172
159
|
end
|
160
|
+
end
|
173
161
|
|
174
|
-
|
175
|
-
|
176
|
-
|
162
|
+
def get(id)
|
163
|
+
file_model.find(id)
|
164
|
+
end
|
177
165
|
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
166
|
+
def delete(id)
|
167
|
+
file_model.find(id).destroy
|
168
|
+
rescue StandardError
|
169
|
+
nil
|
170
|
+
end
|
183
171
|
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
end
|
172
|
+
def where(conditions = {})
|
173
|
+
case conditions
|
174
|
+
when String
|
175
|
+
file_model.where(filename: conditions)
|
176
|
+
else
|
177
|
+
file_model.where(conditions)
|
191
178
|
end
|
179
|
+
end
|
192
180
|
|
193
|
-
|
194
|
-
|
195
|
-
|
181
|
+
def find(*args)
|
182
|
+
where(*args).first
|
183
|
+
end
|
196
184
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
185
|
+
def [](filename)
|
186
|
+
file_model
|
187
|
+
.where(filename: filename.to_s)
|
188
|
+
.order_by(uploadDate: :desc)
|
189
|
+
.limit(1)
|
190
|
+
.first
|
191
|
+
end
|
204
192
|
|
205
|
-
|
206
|
-
|
207
|
-
|
193
|
+
def []=(filename, readable)
|
194
|
+
put(readable, filename: filename.to_s)
|
195
|
+
end
|
208
196
|
|
209
|
-
|
210
|
-
|
211
|
-
|
197
|
+
def clear
|
198
|
+
file_model.destroy_all
|
199
|
+
end
|
212
200
|
|
213
|
-
# TODO - opening with a mode = 'w' should return a GridIO::IOProxy
|
201
|
+
# TODO: - opening with a mode = 'w' should return a GridIO::IOProxy
|
214
202
|
# implementing a StringIO-like interface
|
215
203
|
#
|
216
|
-
|
217
|
-
|
218
|
-
end
|
204
|
+
def open(_filename, _mode = 'r')
|
205
|
+
raise NotImplementedError
|
219
206
|
end
|
220
207
|
end
|
208
|
+
end
|
221
209
|
|
222
210
|
##
|
223
211
|
#
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
end
|
212
|
+
class Defaults < ::Hash
|
213
|
+
def method_missing(method, *args, &block)
|
214
|
+
case method.to_s
|
215
|
+
when /(.*)=/
|
216
|
+
key = Regexp.last_match(1)
|
217
|
+
val = args.first
|
218
|
+
update(key => val)
|
219
|
+
else
|
220
|
+
key = method.to_s
|
221
|
+
super unless key?(key)
|
222
|
+
fetch(key)
|
236
223
|
end
|
237
224
|
end
|
225
|
+
end
|
238
226
|
|
239
227
|
##
|
240
228
|
#
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
Class.new do
|
247
|
-
include Mongoid::Document
|
248
|
-
include Mongoid::Attributes::Dynamic if Mongoid::VERSION.to_i >= 4
|
249
|
-
|
250
|
-
singleton_class = class << self; self; end
|
251
|
-
|
252
|
-
singleton_class.instance_eval do
|
253
|
-
define_method(:name){ file_model_name }
|
254
|
-
attr_accessor :namespace
|
255
|
-
attr_accessor :chunk_model
|
256
|
-
attr_accessor :defaults
|
257
|
-
end
|
229
|
+
def self.build_file_model_for(namespace)
|
230
|
+
prefix = namespace.name.split(/::/).last.downcase
|
231
|
+
file_model_name = "#{namespace.name}::File"
|
232
|
+
chunk_model_name = "#{namespace.name}::Chunk"
|
258
233
|
|
259
|
-
|
234
|
+
Class.new do
|
235
|
+
include Mongoid::Document
|
236
|
+
include Mongoid::Attributes::Dynamic if Mongoid::VERSION.to_i >= 4
|
260
237
|
|
261
|
-
|
238
|
+
singleton_class = class << self; self; end
|
262
239
|
|
263
|
-
|
264
|
-
|
240
|
+
singleton_class.instance_eval do
|
241
|
+
define_method(:name) { file_model_name }
|
242
|
+
attr_accessor :namespace
|
243
|
+
attr_accessor :chunk_model
|
244
|
+
attr_accessor :defaults
|
245
|
+
end
|
265
246
|
|
266
|
-
|
267
|
-
field(:chunkSize, :type => Integer, :default => defaults.chunkSize)
|
268
|
-
field(:uploadDate, :type => Time, :default => Time.now.utc)
|
269
|
-
field(:md5, :type => String, :default => Digest::MD5.hexdigest(''))
|
247
|
+
store_in collection: "#{prefix}.files"
|
270
248
|
|
271
|
-
|
272
|
-
field(:contentType, :type => String, :default => defaults.contentType)
|
273
|
-
field(:aliases, :type => Array)
|
274
|
-
field(:metadata) rescue nil
|
249
|
+
self.defaults = Defaults.new
|
275
250
|
|
276
|
-
|
251
|
+
defaults.chunkSize = 4 * (2**20)
|
252
|
+
defaults.contentType = 'application/octet-stream'
|
277
253
|
|
278
|
-
|
279
|
-
|
280
|
-
|
254
|
+
field(:length, type: Integer, default: 0)
|
255
|
+
field(:chunkSize, type: Integer, default: defaults.chunkSize)
|
256
|
+
field(:uploadDate, type: Time, default: Time.now.utc)
|
257
|
+
field(:md5, type: String, default: Digest::MD5.hexdigest(''))
|
281
258
|
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
259
|
+
field(:filename, type: String)
|
260
|
+
field(:contentType, type: String, default: defaults.contentType)
|
261
|
+
field(:aliases, type: Array)
|
262
|
+
begin
|
263
|
+
field(:metadata)
|
264
|
+
rescue StandardError
|
265
|
+
nil
|
266
|
+
end
|
286
267
|
|
287
|
-
|
268
|
+
required = %w[length chunkSize uploadDate md5]
|
288
269
|
|
289
|
-
|
290
|
-
|
291
|
-
|
270
|
+
required.each do |f|
|
271
|
+
validates_presence_of(f)
|
272
|
+
end
|
292
273
|
|
293
|
-
|
294
|
-
|
295
|
-
|
274
|
+
index(filename: 1)
|
275
|
+
index(aliases: 1)
|
276
|
+
index(uploadDate: 1)
|
277
|
+
index(md5: 1)
|
296
278
|
|
297
|
-
|
298
|
-
return basename if basename
|
279
|
+
has_many(:chunks, class_name: chunk_model_name, inverse_of: :files, dependent: :destroy, order: %i[n asc])
|
299
280
|
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
paths.push(updateDate.iso8601)
|
304
|
-
end
|
281
|
+
def path
|
282
|
+
filename
|
283
|
+
end
|
305
284
|
|
306
|
-
|
307
|
-
|
308
|
-
|
285
|
+
def basename
|
286
|
+
::File.basename(filename) if filename
|
287
|
+
end
|
309
288
|
|
310
|
-
|
311
|
-
|
289
|
+
def attachment_filename(*paths)
|
290
|
+
return basename if basename
|
312
291
|
|
313
|
-
|
314
|
-
|
292
|
+
if paths.empty?
|
293
|
+
paths.push('attachment')
|
294
|
+
paths.push(id.to_s)
|
295
|
+
paths.push(updateDate.iso8601)
|
315
296
|
end
|
316
297
|
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
while fetched < chunks.size
|
321
|
-
chunks.where(:n.lt => fetched+limit, :n.gte => fetched).
|
322
|
-
order_by([:n, :asc]).each do |chunk|
|
323
|
-
block.call(chunk.to_s)
|
324
|
-
end
|
298
|
+
path = paths.join('--')
|
299
|
+
base = ::File.basename(path).split('.', 2).first
|
300
|
+
ext = GridFs.extract_extension(contentType)
|
325
301
|
|
326
|
-
|
327
|
-
|
328
|
-
end
|
302
|
+
"#{base}.#{ext}"
|
303
|
+
end
|
329
304
|
|
330
|
-
|
331
|
-
|
332
|
-
|
333
|
-
range = args.first
|
334
|
-
first_chunk = (range.min / chunkSize).floor
|
335
|
-
last_chunk = (range.max / chunkSize).floor
|
336
|
-
offset = range.min % chunkSize
|
337
|
-
length = range.max - range.min + 1
|
338
|
-
when Fixnum
|
339
|
-
start = args.first
|
340
|
-
start = self.length + start if start < 0
|
341
|
-
length = args.size == 2 ? args.last : 1
|
342
|
-
first_chunk = (start / chunkSize).floor
|
343
|
-
last_chunk = ((start + length) / chunkSize).floor
|
344
|
-
offset = start % chunkSize
|
345
|
-
end
|
305
|
+
def prefix
|
306
|
+
self.class.namespace.prefix
|
307
|
+
end
|
346
308
|
|
347
|
-
|
309
|
+
def each
|
310
|
+
fetched = 0
|
311
|
+
limit = 7
|
348
312
|
|
349
|
-
|
350
|
-
|
313
|
+
while fetched < chunks.size
|
314
|
+
chunks.where(:n.lt => fetched + limit, :n.gte => fetched)
|
315
|
+
.order_by(%i[n asc]).each do |chunk|
|
316
|
+
yield(chunk.to_s)
|
351
317
|
end
|
352
318
|
|
353
|
-
|
319
|
+
fetched += limit
|
354
320
|
end
|
321
|
+
end
|
355
322
|
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
323
|
+
def slice(*args)
|
324
|
+
case args.first
|
325
|
+
when Range
|
326
|
+
range = args.first
|
327
|
+
first_chunk = (range.min / chunkSize).floor
|
328
|
+
last_chunk = (range.max / chunkSize).floor
|
329
|
+
offset = range.min % chunkSize
|
330
|
+
length = range.max - range.min + 1
|
331
|
+
when Integer
|
332
|
+
start = args.first
|
333
|
+
start = self.length + start if start < 0
|
334
|
+
length = args.size == 2 ? args.last : 1
|
335
|
+
first_chunk = (start / chunkSize).floor
|
336
|
+
last_chunk = ((start + length) / chunkSize).floor
|
337
|
+
offset = start % chunkSize
|
360
338
|
end
|
361
339
|
|
362
|
-
|
363
|
-
Array(to_s).pack('m')
|
364
|
-
end
|
340
|
+
data = ''
|
365
341
|
|
366
|
-
|
367
|
-
data
|
368
|
-
"data:#{ content_type };base64,".concat(data)
|
342
|
+
chunks.where(n: (first_chunk..last_chunk)).order_by(n: 'asc').each do |chunk|
|
343
|
+
data << chunk
|
369
344
|
end
|
370
345
|
|
371
|
-
|
372
|
-
|
373
|
-
each{|data| block.call(data)}
|
374
|
-
length
|
375
|
-
else
|
376
|
-
bytes = []
|
377
|
-
each{|data| bytes.push(*data)}
|
378
|
-
bytes
|
379
|
-
end
|
380
|
-
end
|
346
|
+
data[offset, length]
|
347
|
+
end
|
381
348
|
|
382
|
-
|
383
|
-
|
384
|
-
|
349
|
+
def data
|
350
|
+
data = ''
|
351
|
+
each { |chunk| data << chunk }
|
352
|
+
data
|
353
|
+
end
|
385
354
|
|
386
|
-
|
387
|
-
|
388
|
-
|
355
|
+
def base64
|
356
|
+
Array(to_s).pack('m')
|
357
|
+
end
|
389
358
|
|
390
|
-
|
391
|
-
|
392
|
-
|
359
|
+
def data_uri(_options = {})
|
360
|
+
data = base64.chomp
|
361
|
+
"data:#{content_type};base64,".concat(data)
|
362
|
+
end
|
393
363
|
|
394
|
-
|
395
|
-
|
364
|
+
def bytes(&block)
|
365
|
+
if block
|
366
|
+
each { |data| yield(data) }
|
367
|
+
length
|
368
|
+
else
|
369
|
+
bytes = []
|
370
|
+
each { |data| bytes.push(*data) }
|
371
|
+
bytes
|
396
372
|
end
|
373
|
+
end
|
397
374
|
|
398
|
-
|
399
|
-
|
400
|
-
|
375
|
+
def close
|
376
|
+
self
|
377
|
+
end
|
378
|
+
|
379
|
+
def content_type
|
380
|
+
contentType
|
381
|
+
end
|
382
|
+
|
383
|
+
def update_date
|
384
|
+
updateDate
|
385
|
+
end
|
386
|
+
|
387
|
+
def created_at
|
388
|
+
updateDate
|
389
|
+
end
|
390
|
+
|
391
|
+
def namespace
|
392
|
+
self.class.namespace
|
401
393
|
end
|
402
394
|
end
|
395
|
+
end
|
403
396
|
|
404
397
|
##
|
405
398
|
#
|
406
|
-
|
407
|
-
|
408
|
-
|
409
|
-
|
399
|
+
def self.build_chunk_model_for(namespace)
|
400
|
+
prefix = namespace.name.split(/::/).last.downcase
|
401
|
+
file_model_name = "#{namespace.name}::File"
|
402
|
+
chunk_model_name = "#{namespace.name}::Chunk"
|
410
403
|
|
411
|
-
|
412
|
-
|
404
|
+
Class.new do
|
405
|
+
include Mongoid::Document
|
413
406
|
|
414
|
-
|
407
|
+
singleton_class = class << self; self; end
|
415
408
|
|
416
|
-
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
self.store_in :collection => "#{ prefix }.chunks"
|
409
|
+
singleton_class.instance_eval do
|
410
|
+
define_method(:name) { chunk_model_name }
|
411
|
+
attr_accessor :file_model
|
412
|
+
attr_accessor :namespace
|
413
|
+
end
|
423
414
|
|
424
|
-
|
425
|
-
field(:data, :type => (defined?(Moped::BSON) ? Moped::BSON::Binary : BSON::Binary))
|
415
|
+
store_in collection: "#{prefix}.chunks"
|
426
416
|
|
427
|
-
|
417
|
+
field(:n, type: Integer, default: 0)
|
418
|
+
field(:data, type: (defined?(Moped::BSON) ? Moped::BSON::Binary : BSON::Binary))
|
428
419
|
|
429
|
-
|
420
|
+
belongs_to(:file, foreign_key: :files_id, class_name: file_model_name)
|
430
421
|
|
431
|
-
|
432
|
-
self.class.namespace
|
433
|
-
end
|
422
|
+
index({ files_id: 1, n: -1 }, unique: true)
|
434
423
|
|
435
|
-
|
436
|
-
|
437
|
-
|
424
|
+
def namespace
|
425
|
+
self.class.namespace
|
426
|
+
end
|
438
427
|
|
439
|
-
|
428
|
+
def to_s
|
429
|
+
data.data
|
440
430
|
end
|
431
|
+
|
432
|
+
alias_method 'to_str', 'to_s'
|
441
433
|
end
|
434
|
+
end
|
442
435
|
|
443
436
|
##
|
444
437
|
#
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
end
|
438
|
+
def self.reading(arg, &block)
|
439
|
+
if arg.respond_to?(:read)
|
440
|
+
rewind(arg) do |io|
|
441
|
+
block.call(io)
|
442
|
+
end
|
443
|
+
else
|
444
|
+
open(arg.to_s) do |io|
|
445
|
+
block.call(io)
|
454
446
|
end
|
455
447
|
end
|
448
|
+
end
|
456
449
|
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
450
|
+
def self.chunking(io, chunk_size, &block)
|
451
|
+
if io.method(:read).arity.zero?
|
452
|
+
data = io.read
|
453
|
+
i = 0
|
454
|
+
loop do
|
455
|
+
offset = i * chunk_size
|
456
|
+
length = i + chunk_size < data.size ? chunk_size : data.size - offset
|
464
457
|
|
465
|
-
|
458
|
+
break if offset >= data.size
|
466
459
|
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
end
|
460
|
+
buf = data[offset, length]
|
461
|
+
block.call(buf)
|
462
|
+
i += 1
|
463
|
+
end
|
464
|
+
else
|
465
|
+
while (buf = io.read(chunk_size))
|
466
|
+
block.call(buf)
|
475
467
|
end
|
476
468
|
end
|
469
|
+
end
|
477
470
|
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
471
|
+
def self.rewind(io, &block)
|
472
|
+
begin
|
473
|
+
pos = io.pos
|
474
|
+
io.flush
|
475
|
+
io.rewind
|
476
|
+
rescue StandardError
|
477
|
+
nil
|
478
|
+
end
|
486
479
|
|
480
|
+
begin
|
481
|
+
block.call(io)
|
482
|
+
ensure
|
487
483
|
begin
|
488
|
-
|
489
|
-
|
490
|
-
|
491
|
-
io.pos = pos
|
492
|
-
rescue
|
493
|
-
nil
|
494
|
-
end
|
484
|
+
io.pos = pos
|
485
|
+
rescue StandardError
|
486
|
+
nil
|
495
487
|
end
|
496
488
|
end
|
489
|
+
end
|
497
490
|
|
498
|
-
|
499
|
-
|
491
|
+
def self.extract_basename(object)
|
492
|
+
filename = nil
|
500
493
|
|
501
|
-
|
502
|
-
|
503
|
-
|
504
|
-
|
505
|
-
end
|
494
|
+
%i[original_path original_filename path filename pathname].each do |msg|
|
495
|
+
if object.respond_to?(msg)
|
496
|
+
filename = object.send(msg)
|
497
|
+
break
|
506
498
|
end
|
507
|
-
|
508
|
-
filename ? cleanname(filename) : nil
|
509
499
|
end
|
510
500
|
|
511
|
-
|
512
|
-
|
513
|
-
}
|
501
|
+
filename ? cleanname(filename) : nil
|
502
|
+
end
|
514
503
|
|
515
|
-
|
516
|
-
|
517
|
-
|
504
|
+
MIME_TYPES = {
|
505
|
+
'md' => 'text/x-markdown; charset=UTF-8'
|
506
|
+
}.freeze
|
518
507
|
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
basename = ::File.basename(filename.to_s)
|
523
|
-
parts = basename.split('.')
|
524
|
-
parts.shift
|
525
|
-
ext = parts.pop
|
526
|
-
|
527
|
-
default =
|
528
|
-
case
|
529
|
-
when options[:default]==false
|
530
|
-
nil
|
531
|
-
when options[:default]==true
|
532
|
-
"application/octet-stream"
|
533
|
-
else
|
534
|
-
(options[:default] || "application/octet-stream").to_s
|
535
|
-
end
|
508
|
+
def self.mime_types
|
509
|
+
MIME_TYPES
|
510
|
+
end
|
536
511
|
|
537
|
-
|
512
|
+
def self.extract_content_type(filename, options = {})
|
513
|
+
options.to_options!
|
538
514
|
|
539
|
-
|
540
|
-
|
515
|
+
basename = ::File.basename(filename.to_s)
|
516
|
+
parts = basename.split('.')
|
517
|
+
parts.shift
|
518
|
+
ext = parts.pop
|
519
|
+
|
520
|
+
default =
|
521
|
+
if options[:default] == false
|
522
|
+
nil
|
523
|
+
elsif options[:default] == true
|
524
|
+
'application/octet-stream'
|
541
525
|
else
|
542
|
-
default
|
526
|
+
(options[:default] || 'application/octet-stream').to_s
|
543
527
|
end
|
544
|
-
end
|
545
528
|
|
546
|
-
|
547
|
-
list = MIME::Types[content_type.to_s]
|
548
|
-
type = list.first
|
549
|
-
if type
|
550
|
-
type.extensions.first
|
551
|
-
end
|
552
|
-
end
|
529
|
+
content_type = mime_types[ext] || MIME::Types.type_for(::File.basename(filename.to_s)).first
|
553
530
|
|
554
|
-
|
555
|
-
|
556
|
-
|
531
|
+
if content_type
|
532
|
+
content_type.to_s
|
533
|
+
else
|
534
|
+
default
|
557
535
|
end
|
558
536
|
end
|
559
537
|
|
560
|
-
|
561
|
-
|
538
|
+
def self.extract_extension(content_type)
|
539
|
+
list = MIME::Types[content_type.to_s]
|
540
|
+
type = list.first
|
541
|
+
type.extensions.first if type
|
542
|
+
end
|
543
|
+
|
544
|
+
def self.cleanname(pathname)
|
545
|
+
basename = ::File.basename(pathname.to_s)
|
546
|
+
CGI.unescape(basename).gsub(/[^0-9a-zA-Z_@)(~.-]/, '_').gsub(/_+/, '_')
|
547
|
+
end
|
562
548
|
end
|
563
549
|
|
550
|
+
GridFS = GridFs
|
551
|
+
GridFs.init!
|
552
|
+
end
|
553
|
+
|
564
554
|
##
|
565
555
|
#
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
556
|
+
if defined?(Rails)
|
557
|
+
class Mongoid::GridFs::Engine < Rails::Engine
|
558
|
+
paths['app/models'] = File.dirname(File.expand_path(__dir__))
|
559
|
+
end
|
570
560
|
|
571
|
-
|
572
|
-
|
573
|
-
|
561
|
+
module Mongoid::GridFsHelper
|
562
|
+
def grid_fs_render(grid_fs_file, options = {})
|
563
|
+
options.to_options!
|
574
564
|
|
575
|
-
|
576
|
-
headers['Content-Disposition'] = "attachment; filename=#{ grid_fs_file.attachment_filename }"
|
577
|
-
end
|
565
|
+
headers['Content-Disposition'] = "attachment; filename=#{grid_fs_file.attachment_filename}" if (options[:inline] == false) || (options[:attachment] == true)
|
578
566
|
|
579
|
-
|
580
|
-
|
581
|
-
end
|
567
|
+
self.content_type = grid_fs_file.content_type
|
568
|
+
self.response_body = grid_fs_file
|
582
569
|
end
|
583
|
-
|
584
|
-
Mongoid::GridFs::Helper = Mongoid::GridFsHelper
|
585
570
|
end
|
571
|
+
|
572
|
+
Mongoid::GridFs::Helper = Mongoid::GridFsHelper
|
573
|
+
end
|