mongo 0.1.0 → 0.15
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.
- data/README.rdoc +268 -71
- data/Rakefile +27 -62
- data/bin/bson_benchmark.rb +59 -0
- data/bin/mongo_console +3 -3
- data/bin/run_test_script +19 -0
- data/bin/standard_benchmark +109 -0
- data/examples/admin.rb +41 -0
- data/examples/benchmarks.rb +42 -0
- data/examples/blog.rb +76 -0
- data/examples/capped.rb +23 -0
- data/examples/cursor.rb +47 -0
- data/examples/gridfs.rb +87 -0
- data/examples/index_test.rb +125 -0
- data/examples/info.rb +30 -0
- data/examples/queries.rb +69 -0
- data/examples/simple.rb +23 -0
- data/examples/strict.rb +34 -0
- data/examples/types.rb +35 -0
- data/lib/mongo.rb +9 -2
- data/lib/mongo/admin.rb +65 -68
- data/lib/mongo/collection.rb +379 -117
- data/lib/mongo/connection.rb +151 -0
- data/lib/mongo/cursor.rb +271 -216
- data/lib/mongo/db.rb +500 -315
- data/lib/mongo/errors.rb +26 -0
- data/lib/mongo/gridfs.rb +16 -0
- data/lib/mongo/gridfs/chunk.rb +92 -0
- data/lib/mongo/gridfs/grid_store.rb +464 -0
- data/lib/mongo/message.rb +16 -0
- data/lib/mongo/message/get_more_message.rb +24 -13
- data/lib/mongo/message/insert_message.rb +29 -11
- data/lib/mongo/message/kill_cursors_message.rb +23 -12
- data/lib/mongo/message/message.rb +74 -62
- data/lib/mongo/message/message_header.rb +35 -24
- data/lib/mongo/message/msg_message.rb +21 -9
- data/lib/mongo/message/opcodes.rb +26 -15
- data/lib/mongo/message/query_message.rb +63 -43
- data/lib/mongo/message/remove_message.rb +29 -12
- data/lib/mongo/message/update_message.rb +30 -13
- data/lib/mongo/query.rb +97 -89
- data/lib/mongo/types/binary.rb +25 -21
- data/lib/mongo/types/code.rb +30 -0
- data/lib/mongo/types/dbref.rb +19 -23
- data/lib/mongo/types/objectid.rb +130 -116
- data/lib/mongo/types/regexp_of_holding.rb +27 -31
- data/lib/mongo/util/bson.rb +273 -160
- data/lib/mongo/util/byte_buffer.rb +32 -28
- data/lib/mongo/util/ordered_hash.rb +88 -42
- data/lib/mongo/util/xml_to_ruby.rb +18 -15
- data/mongo-ruby-driver.gemspec +103 -0
- data/test/mongo-qa/_common.rb +8 -0
- data/test/mongo-qa/admin +26 -0
- data/test/mongo-qa/capped +22 -0
- data/test/mongo-qa/count1 +18 -0
- data/test/mongo-qa/dbs +22 -0
- data/test/mongo-qa/find +10 -0
- data/test/mongo-qa/find1 +15 -0
- data/test/mongo-qa/gridfs_in +16 -0
- data/test/mongo-qa/gridfs_out +17 -0
- data/test/mongo-qa/indices +49 -0
- data/test/mongo-qa/remove +25 -0
- data/test/mongo-qa/stress1 +35 -0
- data/test/mongo-qa/test1 +11 -0
- data/test/mongo-qa/update +18 -0
- data/{tests → test}/test_admin.rb +25 -16
- data/test/test_bson.rb +268 -0
- data/{tests → test}/test_byte_buffer.rb +0 -0
- data/test/test_chunk.rb +84 -0
- data/test/test_collection.rb +282 -0
- data/test/test_connection.rb +101 -0
- data/test/test_cursor.rb +321 -0
- data/test/test_db.rb +196 -0
- data/test/test_db_api.rb +798 -0
- data/{tests → test}/test_db_connection.rb +4 -3
- data/test/test_grid_store.rb +284 -0
- data/{tests → test}/test_message.rb +1 -1
- data/test/test_objectid.rb +105 -0
- data/{tests → test}/test_ordered_hash.rb +55 -0
- data/{tests → test}/test_round_trip.rb +13 -9
- data/test/test_threading.rb +37 -0
- metadata +74 -32
- data/bin/validate +0 -51
- data/lib/mongo/mongo.rb +0 -74
- data/lib/mongo/types/undefined.rb +0 -31
- data/tests/test_bson.rb +0 -135
- data/tests/test_cursor.rb +0 -66
- data/tests/test_db.rb +0 -51
- data/tests/test_db_api.rb +0 -349
- data/tests/test_objectid.rb +0 -88
data/lib/mongo/errors.rb
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
# Copyright 2009 10gen, Inc.
|
2
|
+
#
|
3
|
+
# Licensed under the Apache License, Version 2.0 (the "License");
|
4
|
+
# you may not use this file except in compliance with the License.
|
5
|
+
# You may obtain a copy of the License at
|
6
|
+
#
|
7
|
+
# http://www.apache.org/licenses/LICENSE-2.0
|
8
|
+
#
|
9
|
+
# Unless required by applicable law or agreed to in writing, software
|
10
|
+
# distributed under the License is distributed on an "AS IS" BASIS,
|
11
|
+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
12
|
+
# See the License for the specific language governing permissions and
|
13
|
+
# limitations under the License.
|
14
|
+
|
15
|
+
# Exceptions raised by the MongoDB driver.
|
16
|
+
|
17
|
+
module Mongo
|
18
|
+
# Raised when a database operation fails.
|
19
|
+
class OperationFailure < RuntimeError; end
|
20
|
+
|
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
|
26
|
+
end
|
data/lib/mongo/gridfs.rb
ADDED
@@ -0,0 +1,16 @@
|
|
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'
|
@@ -0,0 +1,92 @@
|
|
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
|
+
|
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
|
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])
|
65
|
+
end
|
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
|
+
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,464 @@
|
|
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 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.
|
96
|
+
#
|
97
|
+
# :db :: the database to use
|
98
|
+
#
|
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
|
105
|
+
|
106
|
+
def readlines(db, name, separator=$/)
|
107
|
+
GridStore.open(db, name, 'r') { |gs|
|
108
|
+
gs.readlines(separator)
|
109
|
+
}
|
110
|
+
end
|
111
|
+
|
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
|
120
|
+
|
121
|
+
end
|
122
|
+
|
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
|
164
|
+
|
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
|
186
|
+
|
187
|
+
@lineno = 0
|
188
|
+
@pushback_byte = nil
|
189
|
+
end
|
190
|
+
|
191
|
+
def collection
|
192
|
+
@db.collection("#{@root}.files")
|
193
|
+
end
|
194
|
+
|
195
|
+
# Returns collection used for storing chunks. Depends on value of
|
196
|
+
# @root.
|
197
|
+
def chunk_collection
|
198
|
+
@db.collection("#{@root}.chunks")
|
199
|
+
end
|
200
|
+
|
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
|
209
|
+
|
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)
|
225
|
+
end
|
226
|
+
@position += 1
|
227
|
+
@curr_chunk.getc
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
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
|
244
|
+
|
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
|
255
|
+
|
256
|
+
def readchar
|
257
|
+
byte = self.getc
|
258
|
+
raise EOFError.new if byte == nil
|
259
|
+
byte
|
260
|
+
end
|
261
|
+
|
262
|
+
def readline(separator=$/)
|
263
|
+
line = gets
|
264
|
+
raise EOFError.new if line == nil
|
265
|
+
line
|
266
|
+
end
|
267
|
+
|
268
|
+
def readlines(separator=$/)
|
269
|
+
read.split(separator).collect { |line| "#{line}#{separator}" }
|
270
|
+
end
|
271
|
+
|
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
|
280
|
+
|
281
|
+
def each_byte
|
282
|
+
byte = self.getc
|
283
|
+
while byte
|
284
|
+
yield byte
|
285
|
+
byte = self.getc
|
286
|
+
end
|
287
|
+
end
|
288
|
+
|
289
|
+
def ungetc(byte)
|
290
|
+
@pushback_byte = byte
|
291
|
+
@position -= 1
|
292
|
+
end
|
293
|
+
|
294
|
+
#---
|
295
|
+
# ================ writing ================
|
296
|
+
#+++
|
297
|
+
|
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
|
307
|
+
|
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
|
316
|
+
|
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
|
329
|
+
|
330
|
+
def <<(obj)
|
331
|
+
write(obj.to_s)
|
332
|
+
end
|
333
|
+
|
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
|
344
|
+
|
345
|
+
# A no-op.
|
346
|
+
def flush
|
347
|
+
end
|
348
|
+
|
349
|
+
#---
|
350
|
+
# ================ status ================
|
351
|
+
#+++
|
352
|
+
|
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)
|
370
|
+
end
|
371
|
+
end
|
372
|
+
@curr_chunk.pos = 0
|
373
|
+
@lineno = 0
|
374
|
+
@position = 0
|
375
|
+
end
|
376
|
+
|
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
|
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)
|
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 || {})
|
457
|
+
end
|
458
|
+
|
459
|
+
def last_chunk_number
|
460
|
+
(@length / @chunk_size).to_i
|
461
|
+
end
|
462
|
+
|
463
|
+
end
|
464
|
+
end
|