mongo-find_replace 0.18.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. data/LICENSE.txt +202 -0
  2. data/README.rdoc +358 -0
  3. data/Rakefile +133 -0
  4. data/bin/bson_benchmark.rb +59 -0
  5. data/bin/fail_if_no_c.rb +11 -0
  6. data/examples/admin.rb +42 -0
  7. data/examples/capped.rb +22 -0
  8. data/examples/cursor.rb +48 -0
  9. data/examples/gridfs.rb +88 -0
  10. data/examples/index_test.rb +126 -0
  11. data/examples/info.rb +31 -0
  12. data/examples/queries.rb +70 -0
  13. data/examples/simple.rb +24 -0
  14. data/examples/strict.rb +35 -0
  15. data/examples/types.rb +36 -0
  16. data/lib/mongo.rb +61 -0
  17. data/lib/mongo/admin.rb +95 -0
  18. data/lib/mongo/collection.rb +664 -0
  19. data/lib/mongo/connection.rb +555 -0
  20. data/lib/mongo/cursor.rb +393 -0
  21. data/lib/mongo/db.rb +527 -0
  22. data/lib/mongo/exceptions.rb +60 -0
  23. data/lib/mongo/gridfs.rb +22 -0
  24. data/lib/mongo/gridfs/chunk.rb +90 -0
  25. data/lib/mongo/gridfs/grid_store.rb +555 -0
  26. data/lib/mongo/types/binary.rb +48 -0
  27. data/lib/mongo/types/code.rb +36 -0
  28. data/lib/mongo/types/dbref.rb +38 -0
  29. data/lib/mongo/types/min_max_keys.rb +58 -0
  30. data/lib/mongo/types/objectid.rb +219 -0
  31. data/lib/mongo/types/regexp_of_holding.rb +45 -0
  32. data/lib/mongo/util/bson_c.rb +18 -0
  33. data/lib/mongo/util/bson_ruby.rb +595 -0
  34. data/lib/mongo/util/byte_buffer.rb +222 -0
  35. data/lib/mongo/util/conversions.rb +97 -0
  36. data/lib/mongo/util/ordered_hash.rb +135 -0
  37. data/lib/mongo/util/server_version.rb +69 -0
  38. data/lib/mongo/util/support.rb +26 -0
  39. data/lib/mongo/util/xml_to_ruby.rb +112 -0
  40. data/mongo-ruby-driver.gemspec +28 -0
  41. data/test/replica/count_test.rb +34 -0
  42. data/test/replica/insert_test.rb +50 -0
  43. data/test/replica/pooled_insert_test.rb +54 -0
  44. data/test/replica/query_test.rb +39 -0
  45. data/test/test_admin.rb +67 -0
  46. data/test/test_bson.rb +397 -0
  47. data/test/test_byte_buffer.rb +81 -0
  48. data/test/test_chunk.rb +82 -0
  49. data/test/test_collection.rb +534 -0
  50. data/test/test_connection.rb +160 -0
  51. data/test/test_conversions.rb +120 -0
  52. data/test/test_cursor.rb +386 -0
  53. data/test/test_db.rb +254 -0
  54. data/test/test_db_api.rb +783 -0
  55. data/test/test_db_connection.rb +16 -0
  56. data/test/test_grid_store.rb +306 -0
  57. data/test/test_helper.rb +42 -0
  58. data/test/test_objectid.rb +156 -0
  59. data/test/test_ordered_hash.rb +168 -0
  60. data/test/test_round_trip.rb +114 -0
  61. data/test/test_slave_connection.rb +36 -0
  62. data/test/test_threading.rb +87 -0
  63. data/test/threading/test_threading_large_pool.rb +90 -0
  64. data/test/unit/collection_test.rb +52 -0
  65. data/test/unit/connection_test.rb +59 -0
  66. data/test/unit/cursor_test.rb +94 -0
  67. data/test/unit/db_test.rb +97 -0
  68. metadata +123 -0
@@ -0,0 +1,60 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ module Mongo
18
+ # Generic Mongo Ruby Driver exception class.
19
+ class MongoRubyError < StandardError; end
20
+
21
+ # Raised when MongoDB itself has returned an error.
22
+ class MongoDBError < RuntimeError; end
23
+
24
+ # Raised when configuration options cause connections, queries, etc., to fail.
25
+ class ConfigurationError < MongoRubyError; end
26
+
27
+ # Raised when invalid arguments are sent to Mongo Ruby methods.
28
+ class MongoArgumentError < MongoRubyError; end
29
+
30
+ # Raised when given a string is not valid utf-8 (Ruby 1.8 only).
31
+ class InvalidStringEncoding < MongoRubyError; end
32
+
33
+ # Raised when attempting to initialize an invalid ObjectID.
34
+ class InvalidObjectID < MongoRubyError; end
35
+
36
+ # Raised on failures in connection to the database server.
37
+ class ConnectionError < MongoRubyError; end
38
+
39
+ # Raised on failures in connection to the database server.
40
+ class ConnectionTimeoutError < MongoRubyError; end
41
+
42
+ # Raised when trying to insert a document that exceeds the 4MB limit or
43
+ # when the document contains objects that can't be serialized as BSON.
44
+ class InvalidDocument < MongoDBError; end
45
+
46
+ # Raised when a database operation fails.
47
+ class OperationFailure < MongoDBError; end
48
+
49
+ # Raised when a connection operation fails.
50
+ class ConnectionFailure < MongoDBError; end
51
+
52
+ # Raised when a client attempts to perform an invalid operation.
53
+ class InvalidOperation < MongoDBError; end
54
+
55
+ # Raised when an invalid name is used.
56
+ class InvalidName < RuntimeError; end
57
+
58
+ # Raised when the client supplies an invalid value to sort by.
59
+ class InvalidSortValueError < MongoRubyError; end
60
+ end
@@ -0,0 +1,22 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+ require 'mongo/gridfs/grid_store'
17
+
18
+ # GridFS is a specification for storing large binary objects in MongoDB.
19
+ # See the documentation for GridFS::GridStore
20
+ # @see GridFS::GridStore
21
+ module GridFS
22
+ end
@@ -0,0 +1,90 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/types/objectid'
18
+ require 'mongo/util/byte_buffer'
19
+ require 'mongo/util/ordered_hash'
20
+
21
+ module GridFS
22
+
23
+ # A chunk stores a portion of GridStore data.
24
+ class Chunk
25
+
26
+ DEFAULT_CHUNK_SIZE = 1024 * 256
27
+
28
+ attr_reader :object_id, :chunk_number
29
+ attr_accessor :data
30
+
31
+ def initialize(file, mongo_object={})
32
+ @file = file
33
+ @object_id = mongo_object['_id'] || Mongo::ObjectID.new
34
+ @chunk_number = mongo_object['n'] || 0
35
+
36
+ @data = ByteBuffer.new
37
+ case mongo_object['data']
38
+ when String
39
+ mongo_object['data'].each_byte { |b| @data.put(b) }
40
+ when ByteBuffer
41
+ @data.put_array(mongo_object['data'].to_a)
42
+ when Array
43
+ @data.put_array(mongo_object['data'])
44
+ when nil
45
+ else
46
+ raise "illegal chunk format; data is #{mongo_object['data'] ? (' ' + mongo_object['data'].class.name) : 'nil'}"
47
+ end
48
+ @data.rewind
49
+ end
50
+
51
+ def pos; @data.position; end
52
+ def pos=(pos); @data.position = pos; end
53
+ def eof?; !@data.more?; end
54
+
55
+ def size; @data.size; end
56
+ alias_method :length, :size
57
+
58
+ def truncate
59
+ if @data.position < @data.length
60
+ curr_data = @data
61
+ @data = ByteBuffer.new
62
+ @data.put_array(curr_data.to_a[0...curr_data.position])
63
+ end
64
+ end
65
+
66
+ def getc
67
+ @data.more? ? @data.get : nil
68
+ end
69
+
70
+ def putc(byte)
71
+ @data.put(byte)
72
+ end
73
+
74
+ def save
75
+ coll = @file.chunk_collection
76
+ coll.remove({'_id' => @object_id})
77
+ coll.insert(to_mongo_object)
78
+ end
79
+
80
+ def to_mongo_object
81
+ h = OrderedHash.new
82
+ h['_id'] = @object_id
83
+ h['files_id'] = @file.files_id
84
+ h['n'] = @chunk_number
85
+ h['data'] = data
86
+ h
87
+ end
88
+
89
+ end
90
+ end
@@ -0,0 +1,555 @@
1
+ # --
2
+ # Copyright (C) 2008-2009 10gen Inc.
3
+ #
4
+ # Licensed under the Apache License, Version 2.0 (the "License");
5
+ # you may not use this file except in compliance with the License.
6
+ # You may obtain a copy of the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS,
12
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ # See the License for the specific language governing permissions and
14
+ # limitations under the License.
15
+ # ++
16
+
17
+ require 'mongo/types/objectid'
18
+ require 'mongo/util/ordered_hash'
19
+ require 'mongo/gridfs/chunk'
20
+
21
+ module GridFS
22
+
23
+ # GridStore is an IO-like class that provides input and output for
24
+ # streams of data to MongoDB.
25
+ #
26
+ # @example
27
+ #
28
+ # include GridFS
29
+ #
30
+ # #Store the text "Hello, world!" in the grid store.
31
+ # GridStore.open(database, 'filename', 'w') do |f|
32
+ # f.puts "Hello, world!"
33
+ # end
34
+ #
35
+ # # Output "Hello, world!"
36
+ # GridStore.open(database, 'filename', 'r') do |f|
37
+ # puts f.read
38
+ # end
39
+ #
40
+ # # Add text to the grid store.
41
+ # GridStore.open(database, 'filename', 'w+') do |f|
42
+ # f.puts "But wait, there's more!"
43
+ # end
44
+ #
45
+ # # Retrieve everything, outputting "Hello, world!\nBut wait, there's more!\n"
46
+ # GridStore.open(database, 'filename', 'r') do |f|
47
+ # puts f.read
48
+ # end
49
+ class GridStore
50
+
51
+ DEFAULT_ROOT_COLLECTION = 'fs'
52
+ DEFAULT_CONTENT_TYPE = 'text/plain'
53
+
54
+ include Enumerable
55
+
56
+ attr_accessor :filename
57
+
58
+ # Array of strings; may be +nil+
59
+ attr_accessor :aliases
60
+
61
+ # Default is DEFAULT_CONTENT_TYPE
62
+ attr_accessor :content_type
63
+
64
+ # Size of file in bytes
65
+ attr_reader :length
66
+
67
+ attr_accessor :metadata
68
+
69
+ attr_reader :files_id
70
+
71
+ # Time that the file was first saved.
72
+ attr_reader :upload_date
73
+
74
+ attr_reader :chunk_size
75
+
76
+ attr_accessor :lineno
77
+
78
+ attr_reader :md5
79
+
80
+ # Determine whether a given file exists in the GridStore.
81
+ #
82
+ # @param [Mongo::DB] a MongoDB database.
83
+ # @param [String] name the filename.
84
+ # @param [String] root_collection the name of the gridfs root collection.
85
+ #
86
+ # @return [Boolean]
87
+ def self.exist?(db, name, root_collection=DEFAULT_ROOT_COLLECTION)
88
+ db.collection("#{root_collection}.files").find({'filename' => name}).next_document != nil
89
+ end
90
+
91
+ # Open a GridFS file for reading, writing, or appending. Note that
92
+ # this method must be used with a block.
93
+ #
94
+ # @param [Mongo::DB] a MongoDB database.
95
+ # @param [String] name the filename.
96
+ # @param [String] mode one of 'r', 'w', or 'w+' for reading, writing,
97
+ # and appending, respectively.
98
+ # @param [Hash] options any of the options available on
99
+ # GridStore initialization.
100
+ #
101
+ # @see GridStore#initialize.
102
+ # @see The various GridStore class methods, e.g., GridStore.open, GridStore.read etc.
103
+ def self.open(db, name, mode, options={})
104
+ gs = self.new(db, name, mode, options)
105
+ result = nil
106
+ begin
107
+ result = yield gs if block_given?
108
+ ensure
109
+ gs.close
110
+ end
111
+ result
112
+ end
113
+
114
+ # Read a file stored in GridFS.
115
+ #
116
+ # @param [Mongo::DB] db a MongoDB database.
117
+ # @param [String] name the name of the file.
118
+ # @param [Integer] length the number of bytes to read.
119
+ # @param [Integer] offset the number of bytes beyond the
120
+ # beginning of the file to start reading.
121
+ #
122
+ # @return [String] the file data
123
+ def self.read(db, name, length=nil, offset=nil)
124
+ GridStore.open(db, name, 'r') do |gs|
125
+ gs.seek(offset) if offset
126
+ gs.read(length)
127
+ end
128
+ end
129
+
130
+ # List the contents of all GridFS files stored in the given db and
131
+ # root collection.
132
+ #
133
+ # @param [Mongo::DB] db a MongoDB database.
134
+ # @param [String] root_collection the name of the root collection.
135
+ #
136
+ # @return [Array]
137
+ def self.list(db, root_collection=DEFAULT_ROOT_COLLECTION)
138
+ db.collection("#{root_collection}.files").find().map do |f|
139
+ f['filename']
140
+ end
141
+ end
142
+
143
+ # Get each line of data from the specified file
144
+ # as an array of strings.
145
+ #
146
+ # @param [Mongo::DB] db a MongoDB database.
147
+ # @param [String] name the filename.
148
+ # @param [String, Reg] separator
149
+ #
150
+ # @return [Array]
151
+ def self.readlines(db, name, separator=$/)
152
+ GridStore.open(db, name, 'r') do |gs|
153
+ gs.readlines(separator)
154
+ end
155
+ end
156
+
157
+ # Remove one for more files from the given db.
158
+ #
159
+ # @param [Mongo::Database] db a MongoDB database.
160
+ # @param [Array<String>] names the filenames to remove
161
+ #
162
+ # @return [True]
163
+ def self.unlink(db, *names)
164
+ names.each do |name|
165
+ gs = GridStore.new(db, name)
166
+ gs.send(:delete_chunks)
167
+ gs.collection.remove('_id' => gs.files_id)
168
+ end
169
+ end
170
+ class << self
171
+ alias_method :delete, :unlink
172
+ end
173
+
174
+ # Rename a file in this collection. Note that this method uses
175
+ # Collection#update, which means that you will not be notified
176
+ #
177
+ # @param [Mongo::DB] a MongoDB database.
178
+ # @param [String] src the name of the source file.
179
+ # @param [String] dest the name of the destination file.
180
+ # @param [String] root_collection the name of the default root collection.
181
+ def self.mv(db, src, dest, root_collection=DEFAULT_ROOT_COLLECTION)
182
+ db.collection("#{root_collection}.files").update({ :filename => src }, { '$set' => { :filename => dest } })
183
+ end
184
+
185
+ # Initialize a GridStore instance for reading, writing, or modifying a given file.
186
+ # Note that it's often easier to work with the various GridStore class methods (open, read, etc.).
187
+ #
188
+ # @param [Mongo::DB] db a MongoDB database.
189
+ # @param [String] name a filename.
190
+ # @param [String] mode either 'r', 'w', or 'w+' for reading, writing, or appending, respectively.
191
+ #
192
+ # @option options [String] :root DEFAULT_ROOT_COLLECTION ('r', 'w', 'w+') the name of the root collection to use.
193
+ #
194
+ # @option options [String] :metadata ({}) (w, w+) A hash containing any data you want persisted as
195
+ # this file's metadata.
196
+ #
197
+ # @option options [Integer] :chunk_size (Chunk::DEFAULT_CHUNK_SIZE) (w) Sets chunk size for files opened for writing.
198
+ # See also GridStore#chunk_size=.
199
+ #
200
+ # @option options [String] :content_type ('text/plain') Set the content type stored as the
201
+ # file's metadata. See also GridStore#content_type=.
202
+ def initialize(db, name, mode='r', options={})
203
+ @db, @filename, @mode = db, name, mode
204
+ @root = options[:root] || DEFAULT_ROOT_COLLECTION
205
+
206
+ doc = collection.find({'filename' => @filename}).next_document
207
+ if doc
208
+ @files_id = doc['_id']
209
+ @content_type = doc['contentType']
210
+ @chunk_size = doc['chunkSize']
211
+ @upload_date = doc['uploadDate']
212
+ @aliases = doc['aliases']
213
+ @length = doc['length']
214
+ @metadata = doc['metadata']
215
+ @md5 = doc['md5']
216
+ else
217
+ @files_id = Mongo::ObjectID.new
218
+ @content_type = DEFAULT_CONTENT_TYPE
219
+ @chunk_size = Chunk::DEFAULT_CHUNK_SIZE
220
+ @length = 0
221
+ end
222
+
223
+ case mode
224
+ when 'r'
225
+ @curr_chunk = nth_chunk(0)
226
+ @position = 0
227
+ when 'w'
228
+ chunk_collection.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
229
+ delete_chunks
230
+ @curr_chunk = Chunk.new(self, 'n' => 0)
231
+ @content_type = options[:content_type] if options[:content_type]
232
+ @chunk_size = options[:chunk_size] if options[:chunk_size]
233
+ @metadata = options[:metadata] if options[:metadata]
234
+ @position = 0
235
+ when 'w+'
236
+ chunk_collection.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
237
+ @curr_chunk = nth_chunk(last_chunk_number) || Chunk.new(self, 'n' => 0) # might be empty
238
+ @curr_chunk.pos = @curr_chunk.data.length if @curr_chunk
239
+ @metadata = options[:metadata] if options[:metadata]
240
+ @position = @length
241
+ else
242
+ raise "error: illegal mode #{mode}"
243
+ end
244
+
245
+ @lineno = 0
246
+ @pushback_byte = nil
247
+ end
248
+
249
+ # Get the files collection referenced by this GridStore instance.
250
+ #
251
+ # @return [Mongo::Collection]
252
+ def collection
253
+ @db.collection("#{@root}.files")
254
+ end
255
+
256
+ # Get the chunk collection referenced by this GridStore.
257
+ #
258
+ # @return [Mongo::Collection]
259
+ def chunk_collection
260
+ @db.collection("#{@root}.chunks")
261
+ end
262
+
263
+ # Change the chunk size. This is permitted only when the file is opened for write
264
+ # and no data has yet been written.
265
+ #
266
+ # @param [Integer] size the new chunk size, in bytes.
267
+ #
268
+ # @return [Integer] the new chunk size.
269
+ def chunk_size=(size)
270
+ unless @mode[0] == ?w && @position == 0 && @upload_date == nil
271
+ raise "error: can only change chunk size if open for write and no data written."
272
+ end
273
+ @chunk_size = size
274
+ end
275
+
276
+ # ================ reading ================
277
+
278
+ def getc
279
+ if @pushback_byte
280
+ byte = @pushback_byte
281
+ @pushback_byte = nil
282
+ @position += 1
283
+ byte
284
+ elsif eof?
285
+ nil
286
+ else
287
+ if @curr_chunk.eof?
288
+ @curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
289
+ end
290
+ @position += 1
291
+ @curr_chunk.getc
292
+ end
293
+ end
294
+
295
+ def gets(separator=$/)
296
+ str = ''
297
+ byte = self.getc
298
+ return nil if byte == nil # EOF
299
+ while byte != nil
300
+ s = byte.chr
301
+ str << s
302
+ break if s == separator
303
+ byte = self.getc
304
+ end
305
+ @lineno += 1
306
+ str
307
+ end
308
+
309
+ def read(len=nil, buf=nil)
310
+ if len
311
+ read_partial(len, buf)
312
+ else
313
+ read_all(buf)
314
+ end
315
+ end
316
+
317
+ def readchar
318
+ byte = self.getc
319
+ raise EOFError.new if byte == nil
320
+ byte
321
+ end
322
+
323
+ def readline(separator=$/)
324
+ line = gets
325
+ raise EOFError.new if line == nil
326
+ line
327
+ end
328
+
329
+ def readlines(separator=$/)
330
+ read.split(separator).collect { |line| "#{line}#{separator}" }
331
+ end
332
+
333
+ def each
334
+ line = gets
335
+ while line
336
+ yield line
337
+ line = gets
338
+ end
339
+ end
340
+ alias_method :each_line, :each
341
+
342
+ def each_byte
343
+ byte = self.getc
344
+ while byte
345
+ yield byte
346
+ byte = self.getc
347
+ end
348
+ end
349
+
350
+ def ungetc(byte)
351
+ @pushback_byte = byte
352
+ @position -= 1
353
+ end
354
+
355
+ # ================ writing ================
356
+
357
+ def putc(byte)
358
+ if @curr_chunk.pos == @chunk_size
359
+ prev_chunk_number = @curr_chunk.chunk_number
360
+ @curr_chunk.save
361
+ @curr_chunk = Chunk.new(self, 'n' => prev_chunk_number + 1)
362
+ end
363
+ @position += 1
364
+ @curr_chunk.putc(byte)
365
+ end
366
+
367
+ def print(*objs)
368
+ objs = [$_] if objs == nil || objs.empty?
369
+ objs.each { |obj|
370
+ str = obj.to_s
371
+ str.each_byte { |byte| self.putc(byte) }
372
+ }
373
+ nil
374
+ end
375
+
376
+ def puts(*objs)
377
+ if objs == nil || objs.empty?
378
+ self.putc(10)
379
+ else
380
+ print(*objs.collect{ |obj|
381
+ str = obj.to_s
382
+ str << "\n" unless str =~ /\n$/
383
+ str
384
+ })
385
+ end
386
+ nil
387
+ end
388
+
389
+ def <<(obj)
390
+ write(obj.to_s)
391
+ end
392
+
393
+ def write(string)
394
+ raise "#@filename not opened for write" unless @mode[0] == ?w
395
+ # Since Ruby 1.9.1 doesn't necessarily store one character per byte.
396
+ if string.respond_to?(:force_encoding)
397
+ string.force_encoding("binary")
398
+ end
399
+ to_write = string.length
400
+ while (to_write > 0) do
401
+ if @curr_chunk && @curr_chunk.data.position == @chunk_size
402
+ prev_chunk_number = @curr_chunk.chunk_number
403
+ @curr_chunk = GridFS::Chunk.new(self, 'n' => prev_chunk_number + 1)
404
+ end
405
+ chunk_available = @chunk_size - @curr_chunk.data.position
406
+ step_size = (to_write > chunk_available) ? chunk_available : to_write
407
+ @curr_chunk.data.put_array(ByteBuffer.new(string[-to_write,step_size]).to_a)
408
+ to_write -= step_size
409
+ @curr_chunk.save
410
+ end
411
+ string.length - to_write
412
+ end
413
+
414
+ # A no-op.
415
+ def flush
416
+ end
417
+
418
+ # ================ status ================
419
+
420
+ def eof
421
+ raise IOError.new("stream not open for reading") unless @mode[0] == ?r
422
+ @position >= @length
423
+ end
424
+ alias_method :eof?, :eof
425
+
426
+ # ================ positioning ================
427
+
428
+ def rewind
429
+ if @curr_chunk.chunk_number != 0
430
+ if @mode[0] == ?w
431
+ delete_chunks
432
+ @curr_chunk = Chunk.new(self, 'n' => 0)
433
+ else
434
+ @curr_chunk == nth_chunk(0)
435
+ end
436
+ end
437
+ @curr_chunk.pos = 0
438
+ @lineno = 0
439
+ @position = 0
440
+ end
441
+
442
+ def seek(pos, whence=IO::SEEK_SET)
443
+ target_pos = case whence
444
+ when IO::SEEK_CUR
445
+ @position + pos
446
+ when IO::SEEK_END
447
+ @length + pos
448
+ when IO::SEEK_SET
449
+ pos
450
+ end
451
+
452
+ new_chunk_number = (target_pos / @chunk_size).to_i
453
+ if new_chunk_number != @curr_chunk.chunk_number
454
+ @curr_chunk.save if @mode[0] == ?w
455
+ @curr_chunk = nth_chunk(new_chunk_number)
456
+ end
457
+ @position = target_pos
458
+ @curr_chunk.pos = @position % @chunk_size
459
+ 0
460
+ end
461
+
462
+ def tell
463
+ @position
464
+ end
465
+
466
+ #---
467
+ # ================ closing ================
468
+ #+++
469
+
470
+ def close
471
+ if @mode[0] == ?w
472
+ if @curr_chunk
473
+ @curr_chunk.truncate
474
+ @curr_chunk.save if @curr_chunk.pos > 0
475
+ end
476
+ files = collection
477
+ if @upload_date
478
+ files.remove('_id' => @files_id)
479
+ else
480
+ @upload_date = Time.now
481
+ end
482
+ files.insert(to_mongo_object)
483
+ end
484
+ @db = nil
485
+ end
486
+
487
+ def closed?
488
+ @db == nil
489
+ end
490
+
491
+ #---
492
+ # ================ protected ================
493
+ #+++
494
+
495
+ protected
496
+
497
+ def to_mongo_object
498
+ h = OrderedHash.new
499
+ h['_id'] = @files_id
500
+ h['filename'] = @filename
501
+ h['contentType'] = @content_type
502
+ h['length'] = @curr_chunk ? @curr_chunk.chunk_number * @chunk_size + @curr_chunk.pos : 0
503
+ h['chunkSize'] = @chunk_size
504
+ h['uploadDate'] = @upload_date
505
+ h['aliases'] = @aliases
506
+ h['metadata'] = @metadata
507
+ md5_command = OrderedHash.new
508
+ md5_command['filemd5'] = @files_id
509
+ md5_command['root'] = @root
510
+ h['md5'] = @db.command(md5_command)['md5']
511
+ h
512
+ end
513
+
514
+ def read_partial(len, buf=nil)
515
+ buf ||= ''
516
+ byte = self.getc
517
+ while byte != nil && (len == nil || len > 0)
518
+ buf << byte.chr
519
+ len -= 1 if len
520
+ byte = self.getc if (len == nil || len > 0)
521
+ end
522
+ buf
523
+ end
524
+
525
+ def read_all(buf=nil)
526
+ buf ||= ''
527
+ while true do
528
+ if (@curr_chunk.pos > 0)
529
+ data = @curr_chunk.data.to_s
530
+ buf += data[@position, data.length]
531
+ else
532
+ buf += @curr_chunk.data.to_s
533
+ end
534
+ break if @curr_chunk.chunk_number == last_chunk_number
535
+ @curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
536
+ end
537
+ buf
538
+ end
539
+
540
+ def delete_chunks
541
+ chunk_collection.remove({'files_id' => @files_id}) if @files_id
542
+ @curr_chunk = nil
543
+ end
544
+
545
+ def nth_chunk(n)
546
+ mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_document
547
+ Chunk.new(self, mongo_chunk || {})
548
+ end
549
+
550
+ def last_chunk_number
551
+ (@length / @chunk_size).to_i
552
+ end
553
+
554
+ end
555
+ end