mongodb-mongo 0.12 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. data/README.rdoc +12 -12
  2. data/Rakefile +1 -1
  3. data/bin/bson_benchmark.rb +1 -1
  4. data/bin/mongo_console +3 -3
  5. data/bin/run_test_script +2 -2
  6. data/bin/standard_benchmark +3 -3
  7. data/examples/admin.rb +3 -3
  8. data/examples/benchmarks.rb +2 -2
  9. data/examples/blog.rb +4 -4
  10. data/examples/capped.rb +3 -3
  11. data/examples/cursor.rb +3 -3
  12. data/examples/gridfs.rb +4 -4
  13. data/examples/index_test.rb +11 -11
  14. data/examples/info.rb +3 -3
  15. data/examples/queries.rb +3 -3
  16. data/examples/simple.rb +3 -3
  17. data/examples/strict.rb +3 -3
  18. data/examples/types.rb +4 -9
  19. data/lib/mongo.rb +35 -3
  20. data/lib/mongo/admin.rb +56 -60
  21. data/lib/mongo/collection.rb +368 -320
  22. data/lib/mongo/connection.rb +166 -0
  23. data/lib/mongo/cursor.rb +206 -209
  24. data/lib/mongo/db.rb +478 -489
  25. data/lib/mongo/errors.rb +8 -9
  26. data/lib/mongo/gridfs/chunk.rb +66 -70
  27. data/lib/mongo/gridfs/grid_store.rb +406 -410
  28. data/lib/mongo/message/get_more_message.rb +8 -13
  29. data/lib/mongo/message/insert_message.rb +7 -11
  30. data/lib/mongo/message/kill_cursors_message.rb +7 -12
  31. data/lib/mongo/message/message.rb +58 -62
  32. data/lib/mongo/message/message_header.rb +19 -24
  33. data/lib/mongo/message/msg_message.rb +5 -9
  34. data/lib/mongo/message/opcodes.rb +10 -15
  35. data/lib/mongo/message/query_message.rb +42 -46
  36. data/lib/mongo/message/remove_message.rb +8 -12
  37. data/lib/mongo/message/update_message.rb +9 -13
  38. data/lib/mongo/query.rb +84 -88
  39. data/lib/mongo/types/binary.rb +13 -17
  40. data/lib/mongo/types/code.rb +9 -13
  41. data/lib/mongo/types/dbref.rb +10 -14
  42. data/lib/mongo/types/objectid.rb +103 -107
  43. data/lib/mongo/types/regexp_of_holding.rb +18 -22
  44. data/lib/mongo/types/undefined.rb +7 -10
  45. data/lib/mongo/util/bson.rb +4 -9
  46. data/lib/mongo/util/xml_to_ruby.rb +1 -3
  47. data/mongo-ruby-driver.gemspec +33 -32
  48. data/{tests → test}/mongo-qa/_common.rb +1 -1
  49. data/{tests → test}/mongo-qa/admin +1 -1
  50. data/{tests → test}/mongo-qa/capped +1 -1
  51. data/{tests → test}/mongo-qa/count1 +4 -4
  52. data/{tests → test}/mongo-qa/dbs +1 -1
  53. data/{tests → test}/mongo-qa/find +1 -1
  54. data/{tests → test}/mongo-qa/find1 +1 -1
  55. data/{tests → test}/mongo-qa/gridfs_in +2 -2
  56. data/{tests → test}/mongo-qa/gridfs_out +2 -2
  57. data/{tests → test}/mongo-qa/indices +2 -2
  58. data/{tests → test}/mongo-qa/remove +1 -1
  59. data/{tests → test}/mongo-qa/stress1 +1 -1
  60. data/{tests → test}/mongo-qa/test1 +1 -1
  61. data/{tests → test}/mongo-qa/update +1 -1
  62. data/{tests → test}/test_admin.rb +3 -3
  63. data/{tests → test}/test_bson.rb +4 -4
  64. data/{tests → test}/test_byte_buffer.rb +0 -0
  65. data/{tests → test}/test_chunk.rb +4 -4
  66. data/{tests → test}/test_collection.rb +42 -4
  67. data/{tests/test_mongo.rb → test/test_connection.rb} +35 -11
  68. data/test/test_cursor.rb +223 -0
  69. data/{tests → test}/test_db.rb +12 -12
  70. data/{tests → test}/test_db_api.rb +28 -33
  71. data/{tests → test}/test_db_connection.rb +3 -3
  72. data/{tests → test}/test_grid_store.rb +4 -4
  73. data/{tests → test}/test_message.rb +1 -1
  74. data/{tests → test}/test_objectid.rb +3 -3
  75. data/{tests → test}/test_ordered_hash.rb +0 -0
  76. data/{tests → test}/test_round_trip.rb +6 -2
  77. data/{tests → test}/test_threading.rb +3 -3
  78. data/test/test_xgen.rb +73 -0
  79. metadata +33 -32
  80. data/lib/mongo/mongo.rb +0 -164
  81. data/tests/test_cursor.rb +0 -121
data/lib/mongo/errors.rb CHANGED
@@ -14,14 +14,13 @@
14
14
 
15
15
  # Exceptions raised by the MongoDB driver.
16
16
 
17
- module XGen
18
- module Mongo
19
- module Driver
20
- # Raised when a database operation fails.
21
- class OperationFailure < RuntimeError; end
17
+ module Mongo
18
+ # Raised when a database operation fails.
19
+ class OperationFailure < RuntimeError; end
22
20
 
23
- # Raised when an invalid name is used.
24
- class InvalidName < RuntimeError; end
25
- end
26
- end
21
+ # Raised when a client attempts to perform an invalid operation.
22
+ class InvalidOperation < RuntimeError; end
23
+
24
+ # Raised when an invalid name is used.
25
+ class InvalidName < RuntimeError; end
27
26
  end
@@ -19,78 +19,74 @@ require 'mongo/util/byte_buffer'
19
19
  require 'mongo/util/ordered_hash'
20
20
 
21
21
 
22
- module XGen
23
- module Mongo
24
- module GridFS
25
-
26
- # A chunk stores a portion of GridStore data.
27
- class Chunk
28
-
29
- DEFAULT_CHUNK_SIZE = 1024 * 256
30
-
31
- attr_reader :object_id, :chunk_number
32
- attr_accessor :data
33
-
34
- def initialize(file, mongo_object={})
35
- @file = file
36
- @object_id = mongo_object['_id'] || XGen::Mongo::Driver::ObjectID.new
37
- @chunk_number = mongo_object['n'] || 0
38
-
39
- @data = ByteBuffer.new
40
- case mongo_object['data']
41
- when String
42
- mongo_object['data'].each_byte { |b| @data.put(b) }
43
- when ByteBuffer
44
- @data.put_array(mongo_object['data'].to_a)
45
- when Array
46
- @data.put_array(mongo_object['data'])
47
- when nil
48
- else
49
- raise "illegal chunk format; data is #{mongo_object['data'] ? (' ' + mongo_object['data'].class.name) : 'nil'}"
50
- end
51
- @data.rewind
52
- end
53
-
54
- def pos; @data.position; end
55
- def pos=(pos); @data.position = pos; end
56
- def eof?; !@data.more?; end
57
-
58
- def size; @data.size; end
59
- alias_method :length, :size
60
-
61
- # Erase all data after current position.
62
- def truncate
63
- if @data.position < @data.length
64
- curr_data = @data
65
- @data = ByteBuffer.new
66
- @data.put_array(curr_data.to_a[0...curr_data.position])
67
- end
68
- end
69
-
70
- def getc
71
- @data.more? ? @data.get : nil
72
- end
73
-
74
- def putc(byte)
75
- @data.put(byte)
76
- end
77
-
78
- def save
79
- coll = @file.chunk_collection
80
- coll.remove({'_id' => @object_id})
81
- coll.insert(to_mongo_object)
82
- end
83
-
84
- def to_mongo_object
85
- h = OrderedHash.new
86
- h['_id'] = @object_id
87
- h['files_id'] = @file.files_id
88
- h['n'] = @chunk_number
89
- h['data'] = data
90
- h
91
- end
22
+ module GridFS
23
+
24
+ # A chunk stores a portion of GridStore data.
25
+ class Chunk
26
+
27
+ DEFAULT_CHUNK_SIZE = 1024 * 256
28
+
29
+ attr_reader :object_id, :chunk_number
30
+ attr_accessor :data
31
+
32
+ def initialize(file, mongo_object={})
33
+ @file = file
34
+ @object_id = mongo_object['_id'] || Mongo::ObjectID.new
35
+ @chunk_number = mongo_object['n'] || 0
36
+
37
+ @data = ByteBuffer.new
38
+ case mongo_object['data']
39
+ when String
40
+ mongo_object['data'].each_byte { |b| @data.put(b) }
41
+ when ByteBuffer
42
+ @data.put_array(mongo_object['data'].to_a)
43
+ when Array
44
+ @data.put_array(mongo_object['data'])
45
+ when nil
46
+ else
47
+ raise "illegal chunk format; data is #{mongo_object['data'] ? (' ' + mongo_object['data'].class.name) : 'nil'}"
48
+ end
49
+ @data.rewind
50
+ end
51
+
52
+ def pos; @data.position; end
53
+ def pos=(pos); @data.position = pos; end
54
+ def eof?; !@data.more?; end
55
+
56
+ def size; @data.size; end
57
+ alias_method :length, :size
92
58
 
59
+ # Erase all data after current position.
60
+ def truncate
61
+ if @data.position < @data.length
62
+ curr_data = @data
63
+ @data = ByteBuffer.new
64
+ @data.put_array(curr_data.to_a[0...curr_data.position])
93
65
  end
94
66
  end
67
+
68
+ def getc
69
+ @data.more? ? @data.get : nil
70
+ end
71
+
72
+ def putc(byte)
73
+ @data.put(byte)
74
+ end
75
+
76
+ def save
77
+ coll = @file.chunk_collection
78
+ coll.remove({'_id' => @object_id})
79
+ coll.insert(to_mongo_object)
80
+ end
81
+
82
+ def to_mongo_object
83
+ h = OrderedHash.new
84
+ h['_id'] = @object_id
85
+ h['files_id'] = @file.files_id
86
+ h['n'] = @chunk_number
87
+ h['data'] = data
88
+ h
89
+ end
90
+
95
91
  end
96
92
  end
@@ -18,451 +18,447 @@ require 'mongo/types/objectid'
18
18
  require 'mongo/util/ordered_hash'
19
19
  require 'mongo/gridfs/chunk'
20
20
 
21
- module XGen
22
- module Mongo
23
- module GridFS
21
+ module GridFS
24
22
 
25
- # GridStore is an IO-like object that provides input and output for
26
- # streams of data to Mongo. See Mongo's documentation about GridFS for
27
- # storage implementation details.
23
+ # GridStore is an IO-like object that provides input and output for
24
+ # streams of data to Mongo. See Mongo's documentation about GridFS for
25
+ # storage implementation details.
26
+ #
27
+ # Example code:
28
+ #
29
+ # require 'mongo/gridfs'
30
+ # GridStore.open(database, 'filename', 'w') { |f|
31
+ # f.puts "Hello, world!"
32
+ # }
33
+ # GridStore.open(database, 'filename, 'r') { |f|
34
+ # puts f.read # => Hello, world!\n
35
+ # }
36
+ # GridStore.open(database, 'filename', 'w+') { |f|
37
+ # f.puts "But wait, there's more!"
38
+ # }
39
+ # GridStore.open(database, 'filename, 'r') { |f|
40
+ # puts f.read # => Hello, world!\nBut wait, there's more!\n
41
+ # }
42
+ class GridStore
43
+
44
+ DEFAULT_ROOT_COLLECTION = 'fs'
45
+ DEFAULT_CONTENT_TYPE = 'text/plain'
46
+
47
+ include Enumerable
48
+
49
+ attr_accessor :filename
50
+
51
+ # Array of strings; may be +nil+
52
+ attr_accessor :aliases
53
+
54
+ # Default is DEFAULT_CONTENT_TYPE
55
+ attr_accessor :content_type
56
+
57
+ attr_accessor :metadata
58
+
59
+ attr_reader :files_id
60
+
61
+ # Time that the file was first saved.
62
+ attr_reader :upload_date
63
+
64
+ attr_reader :chunk_size
65
+
66
+ attr_accessor :lineno
67
+
68
+ attr_reader :md5
69
+
70
+ class << self
71
+
72
+ def exist?(db, name, root_collection=DEFAULT_ROOT_COLLECTION)
73
+ db.collection("#{root_collection}.files").find({'filename' => name}).next_object != nil
74
+ end
75
+
76
+ def open(db, name, mode, options={})
77
+ gs = self.new(db, name, mode, options)
78
+ result = nil
79
+ begin
80
+ result = yield gs if block_given?
81
+ ensure
82
+ gs.close
83
+ end
84
+ result
85
+ end
86
+
87
+ def read(db, name, length=nil, offset=nil)
88
+ GridStore.open(db, name, 'r') { |gs|
89
+ gs.seek(offset) if offset
90
+ gs.read(length)
91
+ }
92
+ end
93
+
94
+ # List the contains of all GridFS files stored in the given db and
95
+ # root collection.
28
96
  #
29
- # Example code:
97
+ # :db :: the database to use
30
98
  #
31
- # require 'mongo/gridfs'
32
- # GridStore.open(database, 'filename', 'w') { |f|
33
- # f.puts "Hello, world!"
34
- # }
35
- # GridStore.open(database, 'filename, 'r') { |f|
36
- # puts f.read # => Hello, world!\n
37
- # }
38
- # GridStore.open(database, 'filename', 'w+') { |f|
39
- # f.puts "But wait, there's more!"
40
- # }
41
- # GridStore.open(database, 'filename, 'r') { |f|
42
- # puts f.read # => Hello, world!\nBut wait, there's more!\n
43
- # }
44
- class GridStore
45
-
46
- DEFAULT_ROOT_COLLECTION = 'fs'
47
- DEFAULT_CONTENT_TYPE = 'text/plain'
48
-
49
- include Enumerable
50
-
51
- attr_accessor :filename
52
-
53
- # Array of strings; may be +nil+
54
- attr_accessor :aliases
55
-
56
- # Default is DEFAULT_CONTENT_TYPE
57
- attr_accessor :content_type
58
-
59
- attr_accessor :metadata
60
-
61
- attr_reader :files_id
62
-
63
- # Time that the file was first saved.
64
- attr_reader :upload_date
65
-
66
- attr_reader :chunk_size
67
-
68
- attr_accessor :lineno
69
-
70
- attr_reader :md5
71
-
72
- class << self
73
-
74
- def exist?(db, name, root_collection=DEFAULT_ROOT_COLLECTION)
75
- db.collection("#{root_collection}.files").find({'filename' => name}).next_object != nil
76
- end
77
-
78
- def open(db, name, mode, options={})
79
- gs = self.new(db, name, mode, options)
80
- result = nil
81
- begin
82
- result = yield gs if block_given?
83
- ensure
84
- gs.close
85
- end
86
- result
87
- end
88
-
89
- def read(db, name, length=nil, offset=nil)
90
- GridStore.open(db, name, 'r') { |gs|
91
- gs.seek(offset) if offset
92
- gs.read(length)
93
- }
94
- end
95
-
96
- # List the contains of all GridFS files stored in the given db and
97
- # root collection.
98
- #
99
- # :db :: the database to use
100
- #
101
- # :root_collection :: the root collection to use
102
- def list(db, root_collection=DEFAULT_ROOT_COLLECTION)
103
- db.collection("#{root_collection}.files").find().map { |f|
104
- f['filename']
105
- }
106
- end
107
-
108
- def readlines(db, name, separator=$/)
109
- GridStore.open(db, name, 'r') { |gs|
110
- gs.readlines(separator)
111
- }
112
- end
113
-
114
- def unlink(db, *names)
115
- names.each { |name|
116
- gs = GridStore.new(db, name)
117
- gs.send(:delete_chunks)
118
- gs.collection.remove('_id' => gs.files_id)
119
- }
120
- end
121
- alias_method :delete, :unlink
99
+ # :root_collection :: the root collection to use
100
+ def list(db, root_collection=DEFAULT_ROOT_COLLECTION)
101
+ db.collection("#{root_collection}.files").find().map { |f|
102
+ f['filename']
103
+ }
104
+ end
122
105
 
123
- end
106
+ def readlines(db, name, separator=$/)
107
+ GridStore.open(db, name, 'r') { |gs|
108
+ gs.readlines(separator)
109
+ }
110
+ end
124
111
 
125
- #---
126
- # ================================================================
127
- #+++
128
-
129
- # Mode may only be 'r', 'w', or 'w+'.
130
- #
131
- # Options. Descriptions start with a list of the modes for which that
132
- # option is legitimate.
133
- #
134
- # :root :: (r, w, w+) Name of root collection to use, instead of
135
- # DEFAULT_ROOT_COLLECTION.
136
- #
137
- # :metadata:: (w, w+) A hash containing any data you want persisted as
138
- # this file's metadata. See also metadata=
139
- #
140
- # :chunk_size :: (w) Sets chunk size for files opened for writing
141
- # See also chunk_size= which may only be called before
142
- # any data is written.
143
- #
144
- # :content_type :: (w) Default value is DEFAULT_CONTENT_TYPE. See
145
- # also #content_type=
146
- def initialize(db, name, mode='r', options={})
147
- @db, @filename, @mode = db, name, mode
148
- @root = options[:root] || DEFAULT_ROOT_COLLECTION
149
-
150
- doc = collection.find({'filename' => @filename}).next_object
151
- if doc
152
- @files_id = doc['_id']
153
- @content_type = doc['contentType']
154
- @chunk_size = doc['chunkSize']
155
- @upload_date = doc['uploadDate']
156
- @aliases = doc['aliases']
157
- @length = doc['length']
158
- @metadata = doc['metadata']
159
- @md5 = doc['md5']
160
- else
161
- @files_id = XGen::Mongo::Driver::ObjectID.new
162
- @content_type = DEFAULT_CONTENT_TYPE
163
- @chunk_size = Chunk::DEFAULT_CHUNK_SIZE
164
- @length = 0
165
- end
166
-
167
- case mode
168
- when 'r'
169
- @curr_chunk = nth_chunk(0)
170
- @position = 0
171
- when 'w'
172
- chunk_collection.create_index([['files_id', XGen::Mongo::ASCENDING], ['n', XGen::Mongo::ASCENDING]])
173
- delete_chunks
174
- @curr_chunk = Chunk.new(self, 'n' => 0)
175
- @content_type = options[:content_type] if options[:content_type]
176
- @chunk_size = options[:chunk_size] if options[:chunk_size]
177
- @metadata = options[:metadata] if options[:metadata]
178
- @position = 0
179
- when 'w+'
180
- chunk_collection.create_index([['files_id', XGen::Mongo::ASCENDING], ['n', XGen::Mongo::ASCENDING]])
181
- @curr_chunk = nth_chunk(last_chunk_number) || Chunk.new(self, 'n' => 0) # might be empty
182
- @curr_chunk.pos = @curr_chunk.data.length if @curr_chunk
183
- @metadata = options[:metadata] if options[:metadata]
184
- @position = @length
185
- else
186
- raise "error: illegal mode #{mode}"
187
- end
188
-
189
- @lineno = 0
190
- @pushback_byte = nil
191
- end
112
+ def unlink(db, *names)
113
+ names.each { |name|
114
+ gs = GridStore.new(db, name)
115
+ gs.send(:delete_chunks)
116
+ gs.collection.remove('_id' => gs.files_id)
117
+ }
118
+ end
119
+ alias_method :delete, :unlink
192
120
 
193
- def collection
194
- @db.collection("#{@root}.files")
195
- end
121
+ end
196
122
 
197
- # Returns collection used for storing chunks. Depends on value of
198
- # @root.
199
- def chunk_collection
200
- @db.collection("#{@root}.chunks")
201
- end
123
+ #---
124
+ # ================================================================
125
+ #+++
126
+
127
+ # Mode may only be 'r', 'w', or 'w+'.
128
+ #
129
+ # Options. Descriptions start with a list of the modes for which that
130
+ # option is legitimate.
131
+ #
132
+ # :root :: (r, w, w+) Name of root collection to use, instead of
133
+ # DEFAULT_ROOT_COLLECTION.
134
+ #
135
+ # :metadata:: (w, w+) A hash containing any data you want persisted as
136
+ # this file's metadata. See also metadata=
137
+ #
138
+ # :chunk_size :: (w) Sets chunk size for files opened for writing
139
+ # See also chunk_size= which may only be called before
140
+ # any data is written.
141
+ #
142
+ # :content_type :: (w) Default value is DEFAULT_CONTENT_TYPE. See
143
+ # also #content_type=
144
+ def initialize(db, name, mode='r', options={})
145
+ @db, @filename, @mode = db, name, mode
146
+ @root = options[:root] || DEFAULT_ROOT_COLLECTION
147
+
148
+ doc = collection.find({'filename' => @filename}).next_object
149
+ if doc
150
+ @files_id = doc['_id']
151
+ @content_type = doc['contentType']
152
+ @chunk_size = doc['chunkSize']
153
+ @upload_date = doc['uploadDate']
154
+ @aliases = doc['aliases']
155
+ @length = doc['length']
156
+ @metadata = doc['metadata']
157
+ @md5 = doc['md5']
158
+ else
159
+ @files_id = Mongo::ObjectID.new
160
+ @content_type = DEFAULT_CONTENT_TYPE
161
+ @chunk_size = Chunk::DEFAULT_CHUNK_SIZE
162
+ @length = 0
163
+ end
202
164
 
203
- # Change chunk size. Can only change if the file is opened for write
204
- # and no data has yet been written.
205
- def chunk_size=(size)
206
- unless @mode[0] == ?w && @position == 0 && @upload_date == nil
207
- raise "error: can only change chunk size if open for write and no data written."
208
- end
209
- @chunk_size = size
210
- end
165
+ case mode
166
+ when 'r'
167
+ @curr_chunk = nth_chunk(0)
168
+ @position = 0
169
+ when 'w'
170
+ chunk_collection.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
171
+ delete_chunks
172
+ @curr_chunk = Chunk.new(self, 'n' => 0)
173
+ @content_type = options[:content_type] if options[:content_type]
174
+ @chunk_size = options[:chunk_size] if options[:chunk_size]
175
+ @metadata = options[:metadata] if options[:metadata]
176
+ @position = 0
177
+ when 'w+'
178
+ chunk_collection.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
179
+ @curr_chunk = nth_chunk(last_chunk_number) || Chunk.new(self, 'n' => 0) # might be empty
180
+ @curr_chunk.pos = @curr_chunk.data.length if @curr_chunk
181
+ @metadata = options[:metadata] if options[:metadata]
182
+ @position = @length
183
+ else
184
+ raise "error: illegal mode #{mode}"
185
+ end
211
186
 
212
- #---
213
- # ================ reading ================
214
- #+++
215
-
216
- def getc
217
- if @pushback_byte
218
- byte = @pushback_byte
219
- @pushback_byte = nil
220
- @position += 1
221
- byte
222
- elsif eof?
223
- nil
224
- else
225
- if @curr_chunk.eof?
226
- @curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
227
- end
228
- @position += 1
229
- @curr_chunk.getc
230
- end
231
- end
187
+ @lineno = 0
188
+ @pushback_byte = nil
189
+ end
232
190
 
233
- def gets(separator=$/)
234
- str = ''
235
- byte = self.getc
236
- return nil if byte == nil # EOF
237
- while byte != nil
238
- s = byte.chr
239
- str << s
240
- break if s == separator
241
- byte = self.getc
242
- end
243
- @lineno += 1
244
- str
245
- end
191
+ def collection
192
+ @db.collection("#{@root}.files")
193
+ end
246
194
 
247
- def read(len=nil, buf=nil)
248
- buf ||= ''
249
- byte = self.getc
250
- while byte != nil && (len == nil || len > 0)
251
- buf << byte.chr
252
- len -= 1 if len
253
- byte = self.getc if (len == nil || len > 0)
254
- end
255
- buf
256
- end
195
+ # Returns collection used for storing chunks. Depends on value of
196
+ # @root.
197
+ def chunk_collection
198
+ @db.collection("#{@root}.chunks")
199
+ end
257
200
 
258
- def readchar
259
- byte = self.getc
260
- raise EOFError.new if byte == nil
261
- byte
262
- end
201
+ # Change chunk size. Can only change if the file is opened for write
202
+ # and no data has yet been written.
203
+ def chunk_size=(size)
204
+ unless @mode[0] == ?w && @position == 0 && @upload_date == nil
205
+ raise "error: can only change chunk size if open for write and no data written."
206
+ end
207
+ @chunk_size = size
208
+ end
263
209
 
264
- def readline(separator=$/)
265
- line = gets
266
- raise EOFError.new if line == nil
267
- line
210
+ #---
211
+ # ================ reading ================
212
+ #+++
213
+
214
+ def getc
215
+ if @pushback_byte
216
+ byte = @pushback_byte
217
+ @pushback_byte = nil
218
+ @position += 1
219
+ byte
220
+ elsif eof?
221
+ nil
222
+ else
223
+ if @curr_chunk.eof?
224
+ @curr_chunk = nth_chunk(@curr_chunk.chunk_number + 1)
268
225
  end
226
+ @position += 1
227
+ @curr_chunk.getc
228
+ end
229
+ end
269
230
 
270
- def readlines(separator=$/)
271
- read.split(separator).collect { |line| "#{line}#{separator}" }
272
- end
231
+ def gets(separator=$/)
232
+ str = ''
233
+ byte = self.getc
234
+ return nil if byte == nil # EOF
235
+ while byte != nil
236
+ s = byte.chr
237
+ str << s
238
+ break if s == separator
239
+ byte = self.getc
240
+ end
241
+ @lineno += 1
242
+ str
243
+ end
273
244
 
274
- def each
275
- line = gets
276
- while line
277
- yield line
278
- line = gets
279
- end
280
- end
281
- alias_method :each_line, :each
282
-
283
- def each_byte
284
- byte = self.getc
285
- while byte
286
- yield byte
287
- byte = self.getc
288
- end
289
- end
245
+ def read(len=nil, buf=nil)
246
+ buf ||= ''
247
+ byte = self.getc
248
+ while byte != nil && (len == nil || len > 0)
249
+ buf << byte.chr
250
+ len -= 1 if len
251
+ byte = self.getc if (len == nil || len > 0)
252
+ end
253
+ buf
254
+ end
290
255
 
291
- def ungetc(byte)
292
- @pushback_byte = byte
293
- @position -= 1
294
- end
256
+ def readchar
257
+ byte = self.getc
258
+ raise EOFError.new if byte == nil
259
+ byte
260
+ end
295
261
 
296
- #---
297
- # ================ writing ================
298
- #+++
299
-
300
- def putc(byte)
301
- if @curr_chunk.pos == @chunk_size
302
- prev_chunk_number = @curr_chunk.chunk_number
303
- @curr_chunk.save
304
- @curr_chunk = Chunk.new(self, 'n' => prev_chunk_number + 1)
305
- end
306
- @position += 1
307
- @curr_chunk.putc(byte)
308
- end
262
+ def readline(separator=$/)
263
+ line = gets
264
+ raise EOFError.new if line == nil
265
+ line
266
+ end
309
267
 
310
- def print(*objs)
311
- objs = [$_] if objs == nil || objs.empty?
312
- objs.each { |obj|
313
- str = obj.to_s
314
- str.each_byte { |byte| self.putc(byte) }
315
- }
316
- nil
317
- end
268
+ def readlines(separator=$/)
269
+ read.split(separator).collect { |line| "#{line}#{separator}" }
270
+ end
318
271
 
319
- def puts(*objs)
320
- if objs == nil || objs.empty?
321
- self.putc(10)
322
- else
323
- print(*objs.collect{ |obj|
324
- str = obj.to_s
325
- str << "\n" unless str =~ /\n$/
326
- str
327
- })
328
- end
329
- nil
330
- end
272
+ def each
273
+ line = gets
274
+ while line
275
+ yield line
276
+ line = gets
277
+ end
278
+ end
279
+ alias_method :each_line, :each
331
280
 
332
- def <<(obj)
333
- write(obj.to_s)
334
- end
281
+ def each_byte
282
+ byte = self.getc
283
+ while byte
284
+ yield byte
285
+ byte = self.getc
286
+ end
287
+ end
335
288
 
336
- # Writes +string+ as bytes and returns the number of bytes written.
337
- def write(string)
338
- raise "#@filename not opened for write" unless @mode[0] == ?w
339
- count = 0
340
- string.each_byte { |byte|
341
- self.putc byte
342
- count += 1
343
- }
344
- count
345
- end
289
+ def ungetc(byte)
290
+ @pushback_byte = byte
291
+ @position -= 1
292
+ end
346
293
 
347
- # A no-op.
348
- def flush
349
- end
294
+ #---
295
+ # ================ writing ================
296
+ #+++
350
297
 
351
- #---
352
- # ================ status ================
353
- #+++
298
+ def putc(byte)
299
+ if @curr_chunk.pos == @chunk_size
300
+ prev_chunk_number = @curr_chunk.chunk_number
301
+ @curr_chunk.save
302
+ @curr_chunk = Chunk.new(self, 'n' => prev_chunk_number + 1)
303
+ end
304
+ @position += 1
305
+ @curr_chunk.putc(byte)
306
+ end
354
307
 
355
- def eof
356
- raise IOError.new("stream not open for reading") unless @mode[0] == ?r
357
- @position >= @length
358
- end
359
- alias_method :eof?, :eof
360
-
361
- #---
362
- # ================ positioning ================
363
- #+++
364
-
365
- def rewind
366
- if @curr_chunk.chunk_number != 0
367
- if @mode[0] == ?w
368
- delete_chunks
369
- @curr_chunk = Chunk.new(self, 'n' => 0)
370
- else
371
- @curr_chunk == nth_chunk(0)
372
- end
373
- end
374
- @curr_chunk.pos = 0
375
- @lineno = 0
376
- @position = 0
377
- end
308
+ def print(*objs)
309
+ objs = [$_] if objs == nil || objs.empty?
310
+ objs.each { |obj|
311
+ str = obj.to_s
312
+ str.each_byte { |byte| self.putc(byte) }
313
+ }
314
+ nil
315
+ end
378
316
 
379
- def seek(pos, whence=IO::SEEK_SET)
380
- target_pos = case whence
381
- when IO::SEEK_CUR
382
- @position + pos
383
- when IO::SEEK_END
384
- @length + pos
385
- when IO::SEEK_SET
386
- pos
387
- end
388
-
389
- new_chunk_number = (target_pos / @chunk_size).to_i
390
- if new_chunk_number != @curr_chunk.chunk_number
391
- @curr_chunk.save if @mode[0] == ?w
392
- @curr_chunk = nth_chunk(new_chunk_number)
393
- end
394
- @position = target_pos
395
- @curr_chunk.pos = @position % @chunk_size
396
- 0
397
- end
317
+ def puts(*objs)
318
+ if objs == nil || objs.empty?
319
+ self.putc(10)
320
+ else
321
+ print(*objs.collect{ |obj|
322
+ str = obj.to_s
323
+ str << "\n" unless str =~ /\n$/
324
+ str
325
+ })
326
+ end
327
+ nil
328
+ end
398
329
 
399
- def tell
400
- @position
401
- end
402
-
403
- #---
404
- # ================ closing ================
405
- #+++
406
-
407
- def close
408
- if @mode[0] == ?w
409
- if @curr_chunk
410
- @curr_chunk.truncate
411
- @curr_chunk.save if @curr_chunk.pos > 0
412
- end
413
- files = collection
414
- if @upload_date
415
- files.remove('_id' => @files_id)
416
- else
417
- @upload_date = Time.now
418
- end
419
- files.insert(to_mongo_object)
420
- end
421
- @db = nil
422
- end
330
+ def <<(obj)
331
+ write(obj.to_s)
332
+ end
423
333
 
424
- def closed?
425
- @db == nil
426
- end
334
+ # Writes +string+ as bytes and returns the number of bytes written.
335
+ def write(string)
336
+ raise "#@filename not opened for write" unless @mode[0] == ?w
337
+ count = 0
338
+ string.each_byte { |byte|
339
+ self.putc byte
340
+ count += 1
341
+ }
342
+ count
343
+ end
427
344
 
428
- #---
429
- # ================ protected ================
430
- #+++
431
-
432
- protected
433
-
434
- def to_mongo_object
435
- h = OrderedHash.new
436
- h['_id'] = @files_id
437
- h['filename'] = @filename
438
- h['contentType'] = @content_type
439
- h['length'] = @curr_chunk ? @curr_chunk.chunk_number * @chunk_size + @curr_chunk.pos : 0
440
- h['chunkSize'] = @chunk_size
441
- h['uploadDate'] = @upload_date
442
- h['aliases'] = @aliases
443
- h['metadata'] = @metadata
444
- md5_command = OrderedHash.new
445
- md5_command['filemd5'] = @files_id
446
- md5_command['root'] = @root
447
- h['md5'] = @db.db_command(md5_command)['md5']
448
- h
449
- end
345
+ # A no-op.
346
+ def flush
347
+ end
450
348
 
451
- def delete_chunks
452
- chunk_collection.remove({'files_id' => @files_id}) if @files_id
453
- @curr_chunk = nil
454
- end
349
+ #---
350
+ # ================ status ================
351
+ #+++
455
352
 
456
- def nth_chunk(n)
457
- mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_object
458
- Chunk.new(self, mongo_chunk || {})
353
+ def eof
354
+ raise IOError.new("stream not open for reading") unless @mode[0] == ?r
355
+ @position >= @length
356
+ end
357
+ alias_method :eof?, :eof
358
+
359
+ #---
360
+ # ================ positioning ================
361
+ #+++
362
+
363
+ def rewind
364
+ if @curr_chunk.chunk_number != 0
365
+ if @mode[0] == ?w
366
+ delete_chunks
367
+ @curr_chunk = Chunk.new(self, 'n' => 0)
368
+ else
369
+ @curr_chunk == nth_chunk(0)
459
370
  end
371
+ end
372
+ @curr_chunk.pos = 0
373
+ @lineno = 0
374
+ @position = 0
375
+ end
460
376
 
461
- def last_chunk_number
462
- (@length / @chunk_size).to_i
463
- end
377
+ def seek(pos, whence=IO::SEEK_SET)
378
+ target_pos = case whence
379
+ when IO::SEEK_CUR
380
+ @position + pos
381
+ when IO::SEEK_END
382
+ @length + pos
383
+ when IO::SEEK_SET
384
+ pos
385
+ end
386
+
387
+ new_chunk_number = (target_pos / @chunk_size).to_i
388
+ if new_chunk_number != @curr_chunk.chunk_number
389
+ @curr_chunk.save if @mode[0] == ?w
390
+ @curr_chunk = nth_chunk(new_chunk_number)
391
+ end
392
+ @position = target_pos
393
+ @curr_chunk.pos = @position % @chunk_size
394
+ 0
395
+ end
464
396
 
397
+ def tell
398
+ @position
399
+ end
400
+
401
+ #---
402
+ # ================ closing ================
403
+ #+++
404
+
405
+ def close
406
+ if @mode[0] == ?w
407
+ if @curr_chunk
408
+ @curr_chunk.truncate
409
+ @curr_chunk.save if @curr_chunk.pos > 0
410
+ end
411
+ files = collection
412
+ if @upload_date
413
+ files.remove('_id' => @files_id)
414
+ else
415
+ @upload_date = Time.now
416
+ end
417
+ files.insert(to_mongo_object)
465
418
  end
419
+ @db = nil
420
+ end
421
+
422
+ def closed?
423
+ @db == nil
424
+ end
425
+
426
+ #---
427
+ # ================ protected ================
428
+ #+++
429
+
430
+ protected
431
+
432
+ def to_mongo_object
433
+ h = OrderedHash.new
434
+ h['_id'] = @files_id
435
+ h['filename'] = @filename
436
+ h['contentType'] = @content_type
437
+ h['length'] = @curr_chunk ? @curr_chunk.chunk_number * @chunk_size + @curr_chunk.pos : 0
438
+ h['chunkSize'] = @chunk_size
439
+ h['uploadDate'] = @upload_date
440
+ h['aliases'] = @aliases
441
+ h['metadata'] = @metadata
442
+ md5_command = OrderedHash.new
443
+ md5_command['filemd5'] = @files_id
444
+ md5_command['root'] = @root
445
+ h['md5'] = @db.db_command(md5_command)['md5']
446
+ h
447
+ end
448
+
449
+ def delete_chunks
450
+ chunk_collection.remove({'files_id' => @files_id}) if @files_id
451
+ @curr_chunk = nil
452
+ end
453
+
454
+ def nth_chunk(n)
455
+ mongo_chunk = chunk_collection.find({'files_id' => @files_id, 'n' => n}).next_object
456
+ Chunk.new(self, mongo_chunk || {})
466
457
  end
458
+
459
+ def last_chunk_number
460
+ (@length / @chunk_size).to_i
461
+ end
462
+
467
463
  end
468
464
  end