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.
- data/README.rdoc +12 -12
- data/Rakefile +1 -1
- data/bin/bson_benchmark.rb +1 -1
- data/bin/mongo_console +3 -3
- data/bin/run_test_script +2 -2
- data/bin/standard_benchmark +3 -3
- data/examples/admin.rb +3 -3
- data/examples/benchmarks.rb +2 -2
- data/examples/blog.rb +4 -4
- data/examples/capped.rb +3 -3
- data/examples/cursor.rb +3 -3
- data/examples/gridfs.rb +4 -4
- data/examples/index_test.rb +11 -11
- data/examples/info.rb +3 -3
- data/examples/queries.rb +3 -3
- data/examples/simple.rb +3 -3
- data/examples/strict.rb +3 -3
- data/examples/types.rb +4 -9
- data/lib/mongo.rb +35 -3
- data/lib/mongo/admin.rb +56 -60
- data/lib/mongo/collection.rb +368 -320
- data/lib/mongo/connection.rb +166 -0
- data/lib/mongo/cursor.rb +206 -209
- data/lib/mongo/db.rb +478 -489
- data/lib/mongo/errors.rb +8 -9
- data/lib/mongo/gridfs/chunk.rb +66 -70
- data/lib/mongo/gridfs/grid_store.rb +406 -410
- data/lib/mongo/message/get_more_message.rb +8 -13
- data/lib/mongo/message/insert_message.rb +7 -11
- data/lib/mongo/message/kill_cursors_message.rb +7 -12
- data/lib/mongo/message/message.rb +58 -62
- data/lib/mongo/message/message_header.rb +19 -24
- data/lib/mongo/message/msg_message.rb +5 -9
- data/lib/mongo/message/opcodes.rb +10 -15
- data/lib/mongo/message/query_message.rb +42 -46
- data/lib/mongo/message/remove_message.rb +8 -12
- data/lib/mongo/message/update_message.rb +9 -13
- data/lib/mongo/query.rb +84 -88
- data/lib/mongo/types/binary.rb +13 -17
- data/lib/mongo/types/code.rb +9 -13
- data/lib/mongo/types/dbref.rb +10 -14
- data/lib/mongo/types/objectid.rb +103 -107
- data/lib/mongo/types/regexp_of_holding.rb +18 -22
- data/lib/mongo/types/undefined.rb +7 -10
- data/lib/mongo/util/bson.rb +4 -9
- data/lib/mongo/util/xml_to_ruby.rb +1 -3
- data/mongo-ruby-driver.gemspec +33 -32
- data/{tests → test}/mongo-qa/_common.rb +1 -1
- data/{tests → test}/mongo-qa/admin +1 -1
- data/{tests → test}/mongo-qa/capped +1 -1
- data/{tests → test}/mongo-qa/count1 +4 -4
- data/{tests → test}/mongo-qa/dbs +1 -1
- data/{tests → test}/mongo-qa/find +1 -1
- data/{tests → test}/mongo-qa/find1 +1 -1
- data/{tests → test}/mongo-qa/gridfs_in +2 -2
- data/{tests → test}/mongo-qa/gridfs_out +2 -2
- data/{tests → test}/mongo-qa/indices +2 -2
- data/{tests → test}/mongo-qa/remove +1 -1
- data/{tests → test}/mongo-qa/stress1 +1 -1
- data/{tests → test}/mongo-qa/test1 +1 -1
- data/{tests → test}/mongo-qa/update +1 -1
- data/{tests → test}/test_admin.rb +3 -3
- data/{tests → test}/test_bson.rb +4 -4
- data/{tests → test}/test_byte_buffer.rb +0 -0
- data/{tests → test}/test_chunk.rb +4 -4
- data/{tests → test}/test_collection.rb +42 -4
- data/{tests/test_mongo.rb → test/test_connection.rb} +35 -11
- data/test/test_cursor.rb +223 -0
- data/{tests → test}/test_db.rb +12 -12
- data/{tests → test}/test_db_api.rb +28 -33
- data/{tests → test}/test_db_connection.rb +3 -3
- data/{tests → test}/test_grid_store.rb +4 -4
- data/{tests → test}/test_message.rb +1 -1
- data/{tests → test}/test_objectid.rb +3 -3
- data/{tests → test}/test_ordered_hash.rb +0 -0
- data/{tests → test}/test_round_trip.rb +6 -2
- data/{tests → test}/test_threading.rb +3 -3
- data/test/test_xgen.rb +73 -0
- metadata +33 -32
- data/lib/mongo/mongo.rb +0 -164
- 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
|
18
|
-
|
19
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
data/lib/mongo/gridfs/chunk.rb
CHANGED
@@ -19,78 +19,74 @@ require 'mongo/util/byte_buffer'
|
|
19
19
|
require 'mongo/util/ordered_hash'
|
20
20
|
|
21
21
|
|
22
|
-
module
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
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
|
22
|
-
module Mongo
|
23
|
-
module GridFS
|
21
|
+
module GridFS
|
24
22
|
|
25
|
-
|
26
|
-
|
27
|
-
|
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
|
-
#
|
97
|
+
# :db :: the database to use
|
30
98
|
#
|
31
|
-
#
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
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
|
-
|
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
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|
-
|
194
|
-
@db.collection("#{@root}.files")
|
195
|
-
end
|
121
|
+
end
|
196
122
|
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
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
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
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
|
-
|
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
|
-
|
234
|
-
|
235
|
-
|
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
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
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
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
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
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
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
|
-
|
271
|
-
|
272
|
-
|
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
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
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
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
256
|
+
def readchar
|
257
|
+
byte = self.getc
|
258
|
+
raise EOFError.new if byte == nil
|
259
|
+
byte
|
260
|
+
end
|
295
261
|
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
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
|
-
|
311
|
-
|
312
|
-
|
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
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
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
|
-
|
333
|
-
|
334
|
-
|
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
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
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
|
-
|
348
|
-
|
349
|
-
|
294
|
+
#---
|
295
|
+
# ================ writing ================
|
296
|
+
#+++
|
350
297
|
|
351
|
-
|
352
|
-
|
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
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
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
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
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
|
-
|
400
|
-
|
401
|
-
|
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
|
-
|
425
|
-
|
426
|
-
|
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
|
-
|
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
|
-
|
452
|
-
|
453
|
-
|
454
|
-
end
|
349
|
+
#---
|
350
|
+
# ================ status ================
|
351
|
+
#+++
|
455
352
|
|
456
|
-
|
457
|
-
|
458
|
-
|
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
|
-
|
462
|
-
|
463
|
-
|
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
|