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