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