gnuside-mongoid-grid_fs 1.9.1

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