mongodb-mongo 0.12 → 0.13

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.
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