mongo 0.18.3 → 0.19

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 (62) hide show
  1. data/README.rdoc +41 -50
  2. data/Rakefile +14 -4
  3. data/examples/gridfs.rb +25 -70
  4. data/lib/mongo.rb +4 -2
  5. data/lib/mongo/collection.rb +70 -89
  6. data/lib/mongo/connection.rb +203 -43
  7. data/lib/mongo/cursor.rb +7 -7
  8. data/lib/mongo/db.rb +61 -18
  9. data/lib/mongo/exceptions.rb +7 -1
  10. data/lib/mongo/gridfs.rb +8 -1
  11. data/lib/mongo/gridfs/chunk.rb +2 -1
  12. data/lib/mongo/gridfs/grid.rb +90 -0
  13. data/lib/mongo/gridfs/grid_file_system.rb +113 -0
  14. data/lib/mongo/gridfs/grid_io.rb +339 -0
  15. data/lib/mongo/gridfs/grid_store.rb +43 -18
  16. data/lib/mongo/types/binary.rb +5 -1
  17. data/lib/mongo/types/code.rb +1 -1
  18. data/lib/mongo/types/dbref.rb +3 -1
  19. data/lib/mongo/types/min_max_keys.rb +1 -1
  20. data/lib/mongo/types/objectid.rb +16 -55
  21. data/lib/mongo/types/regexp_of_holding.rb +1 -1
  22. data/lib/mongo/util/bson_c.rb +2 -2
  23. data/lib/mongo/util/bson_ruby.rb +22 -11
  24. data/lib/mongo/util/byte_buffer.rb +1 -1
  25. data/lib/mongo/util/conversions.rb +1 -1
  26. data/lib/mongo/util/ordered_hash.rb +6 -1
  27. data/lib/mongo/util/server_version.rb +1 -1
  28. data/lib/mongo/util/support.rb +1 -1
  29. data/mongo-ruby-driver.gemspec +1 -1
  30. data/test/auxillary/authentication_test.rb +68 -0
  31. data/test/auxillary/autoreconnect_test.rb +41 -0
  32. data/test/binary_test.rb +15 -0
  33. data/test/{test_bson.rb → bson_test.rb} +63 -6
  34. data/test/{test_byte_buffer.rb → byte_buffer_test.rb} +0 -0
  35. data/test/{test_chunk.rb → chunk_test.rb} +0 -0
  36. data/test/{test_collection.rb → collection_test.rb} +35 -39
  37. data/test/{test_connection.rb → connection_test.rb} +33 -3
  38. data/test/{test_conversions.rb → conversions_test.rb} +0 -0
  39. data/test/{test_cursor.rb → cursor_test.rb} +0 -7
  40. data/test/{test_db_api.rb → db_api_test.rb} +3 -6
  41. data/test/{test_db_connection.rb → db_connection_test.rb} +0 -0
  42. data/test/{test_db.rb → db_test.rb} +33 -15
  43. data/test/grid_file_system_test.rb +210 -0
  44. data/test/grid_io_test.rb +78 -0
  45. data/test/{test_grid_store.rb → grid_store_test.rb} +33 -2
  46. data/test/grid_test.rb +87 -0
  47. data/test/{test_objectid.rb → objectid_test.rb} +2 -33
  48. data/test/{test_ordered_hash.rb → ordered_hash_test.rb} +4 -0
  49. data/test/{test_slave_connection.rb → slave_connection_test.rb} +0 -0
  50. data/test/test_helper.rb +2 -2
  51. data/test/{test_threading.rb → threading_test.rb} +0 -0
  52. data/test/unit/collection_test.rb +12 -3
  53. data/test/unit/connection_test.rb +85 -24
  54. data/test/unit/cursor_test.rb +1 -2
  55. data/test/unit/db_test.rb +70 -69
  56. metadata +27 -23
  57. data/bin/objectid_benchmark.rb +0 -23
  58. data/bin/perf.rb +0 -30
  59. data/lib/mongo/admin.rb +0 -95
  60. data/lib/mongo/util/xml_to_ruby.rb +0 -112
  61. data/test/test_admin.rb +0 -67
  62. data/test/test_round_trip.rb +0 -114
@@ -1,5 +1,5 @@
1
1
  # --
2
- # Copyright (C) 2008-2009 10gen Inc.
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -24,6 +24,9 @@ module Mongo
24
24
  # Raised when configuration options cause connections, queries, etc., to fail.
25
25
  class ConfigurationError < MongoRubyError; end
26
26
 
27
+ # Raised with fatal errors to GridFS.
28
+ class GridError < MongoRubyError; end
29
+
27
30
  # Raised when invalid arguments are sent to Mongo Ruby methods.
28
31
  class MongoArgumentError < MongoRubyError; end
29
32
 
@@ -43,6 +46,9 @@ module Mongo
43
46
  # when the document contains objects that can't be serialized as BSON.
44
47
  class InvalidDocument < MongoDBError; end
45
48
 
49
+ # Raised when authentication fails.
50
+ class AuthenticationError < MongoDBError; end
51
+
46
52
  # Raised when a database operation fails.
47
53
  class OperationFailure < MongoDBError; end
48
54
 
data/lib/mongo/gridfs.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # --
2
- # Copyright (C) 2008-2009 10gen Inc.
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -15,8 +15,15 @@
15
15
  # ++
16
16
  require 'mongo/gridfs/grid_store'
17
17
 
18
+ # DEPRECATED. Plese see GridFileSystem and Grid classes.
19
+ #
18
20
  # GridFS is a specification for storing large binary objects in MongoDB.
19
21
  # See the documentation for GridFS::GridStore
22
+ #
20
23
  # @see GridFS::GridStore
24
+ #
25
+ # @core gridfs
26
+ #
27
+ # @deprecated
21
28
  module GridFS
22
29
  end
@@ -1,5 +1,5 @@
1
1
  # --
2
- # Copyright (C) 2008-2009 10gen Inc.
2
+ # Copyright (C) 2008-2010 10gen Inc.
3
3
  #
4
4
  # Licensed under the Apache License, Version 2.0 (the "License");
5
5
  # you may not use this file except in compliance with the License.
@@ -21,6 +21,7 @@ require 'mongo/util/ordered_hash'
21
21
  module GridFS
22
22
 
23
23
  # A chunk stores a portion of GridStore data.
24
+ # @deprecated
24
25
  class Chunk
25
26
 
26
27
  DEFAULT_CHUNK_SIZE = 1024 * 256
@@ -0,0 +1,90 @@
1
+ # --
2
+ # Copyright (C) 2008-2010 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
+
19
+ # Implementation of the MongoDB GridFS specification. A file store.
20
+ class Grid
21
+ DEFAULT_FS_NAME = 'fs'
22
+
23
+ # Initialize a new Grid instance, consisting of a MongoDB database
24
+ # and a filesystem prefix if not using the default.
25
+ #
26
+ # @core gridfs
27
+ #
28
+ # @see GridFileSystem
29
+ def initialize(db, fs_name=DEFAULT_FS_NAME)
30
+ raise MongoArgumentError, "db must be a Mongo::DB." unless db.is_a?(Mongo::DB)
31
+
32
+ @db = db
33
+ @files = @db["#{fs_name}.files"]
34
+ @chunks = @db["#{fs_name}.chunks"]
35
+ @fs_name = fs_name
36
+
37
+ @chunks.create_index([['files_id', Mongo::ASCENDING], ['n', Mongo::ASCENDING]])
38
+ end
39
+
40
+ # Store a file in the file store.
41
+ #
42
+ # @param [String, #read] data a string or io-like object to store.
43
+ # @param [String] filename a name for the file.
44
+ #
45
+ # @options opts [Hash] :metadata ({}) any additional data to store with the file.
46
+ # @options opts [ObjectID] :_id (ObjectID) a unique id for
47
+ # the file to be use in lieu of an automatically generated one.
48
+ # @options opts [String] :content_type ('binary/octet-stream') If no content type is specified,
49
+ # the content type will may be inferred from the filename extension if the mime-types gem can be
50
+ # loaded. Otherwise, the content type 'binary/octet-stream' will be used.
51
+ # @options opts [Integer] (262144) :chunk_size size of file chunks in bytes.
52
+ # @options opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
53
+ # will be validated using an md5 hash. If validation fails, an exception will be raised.
54
+ #
55
+ # @return [Mongo::ObjectID] the file's id.
56
+ def put(data, filename, opts={})
57
+ opts.merge!(default_grid_io_opts)
58
+ file = GridIO.new(@files, @chunks, filename, 'w', opts=opts)
59
+ file.write(data)
60
+ file.close
61
+ file.files_id
62
+ end
63
+
64
+ # Read a file from the file store.
65
+ #
66
+ # @param [] id the file's unique id.
67
+ #
68
+ # @return [Mongo::GridIO]
69
+ def get(id)
70
+ opts = {:query => {'_id' => id}}.merge!(default_grid_io_opts)
71
+ GridIO.new(@files, @chunks, nil, 'r', opts)
72
+ end
73
+
74
+ # Delete a file from the store.
75
+ #
76
+ # @param [] id
77
+ #
78
+ # @return [Boolean]
79
+ def delete(id)
80
+ @files.remove({"_id" => id})
81
+ @chunks.remove({"_id" => id})
82
+ end
83
+
84
+ private
85
+
86
+ def default_grid_io_opts
87
+ {:fs_name => @fs_name}
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,113 @@
1
+ # --
2
+ # Copyright (C) 2008-2010 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
+
19
+ # A file store built on the GridFS specification featuring
20
+ # an API and behavior similar to that of a traditional file system.
21
+ class GridFileSystem
22
+
23
+ # Initialize a new Grid instance, consisting of a MongoDB database
24
+ # and a filesystem prefix if not using the default.
25
+ #
26
+ # @param [Mongo::DB] db a MongoDB database.
27
+ # @param [String] fs_name A name for the file system. The default name, based on
28
+ # the specification, is 'fs'.
29
+ def initialize(db, fs_name=Grid::DEFAULT_FS_NAME)
30
+ raise MongoArgumentError, "db must be a Mongo::DB." unless db.is_a?(Mongo::DB)
31
+
32
+ @db = db
33
+ @files = @db["#{fs_name}.files"]
34
+ @chunks = @db["#{fs_name}.chunks"]
35
+ @fs_name = fs_name
36
+
37
+ @files.create_index([['filename', 1], ['uploadDate', -1]])
38
+ @default_query_opts = {:sort => [['filename', 1], ['uploadDate', -1]], :limit => 1}
39
+ end
40
+
41
+ # Open a file for reading or writing. Note that the options for this method only apply
42
+ # when opening in 'w' mode.
43
+ #
44
+ # @param [String] filename the name of the file.
45
+ # @param [String] mode either 'r' or 'w' for reading from
46
+ # or writing to the file.
47
+ # @param [Hash] opts see GridIO#new
48
+ #
49
+ # @options opts [Hash] :metadata ({}) any additional data to store with the file.
50
+ # @options opts [ObjectID] :_id (ObjectID) a unique id for
51
+ # the file to be use in lieu of an automatically generated one.
52
+ # @options opts [String] :content_type ('binary/octet-stream') If no content type is specified,
53
+ # the content type will may be inferred from the filename extension if the mime-types gem can be
54
+ # loaded. Otherwise, the content type 'binary/octet-stream' will be used.
55
+ # @options opts [Integer] (262144) :chunk_size size of file chunks in bytes.
56
+ # @options opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
57
+ # will be validated using an md5 hash. If validation fails, an exception will be raised.
58
+ #
59
+ # @example
60
+ #
61
+ # # Store the text "Hello, world!" in the grid file system.
62
+ # @grid = GridFileSystem.new(@db)
63
+ # @grid.open('filename', 'w') do |f|
64
+ # f.write "Hello, world!"
65
+ # end
66
+ #
67
+ # # Output "Hello, world!"
68
+ # @grid = GridFileSystem.new(@db)
69
+ # @grid.open('filename', 'r') do |f|
70
+ # puts f.read
71
+ # end
72
+ #
73
+ # # Write a file on disk to the GridFileSystem
74
+ # @file = File.open('image.jpg')
75
+ # @grid = GridFileSystem.new(@db)
76
+ # @grid.open('image.jpg, 'w') do |f|
77
+ # f.write @file
78
+ # end
79
+ def open(filename, mode, opts={})
80
+ opts.merge!(default_grid_io_opts(filename))
81
+ file = GridIO.new(@files, @chunks, filename, mode, opts)
82
+ return file unless block_given?
83
+ result = nil
84
+ begin
85
+ result = yield file
86
+ ensure
87
+ file.close
88
+ end
89
+ result
90
+ end
91
+
92
+ # Delete the file with the given filename. Note that this will delete
93
+ # all versions of the file.
94
+ #
95
+ # @param [String] filename
96
+ #
97
+ # @return [Boolean]
98
+ def delete(filename)
99
+ files = @files.find({'filename' => filename}, :fields => ['_id'])
100
+ files.each do |file|
101
+ @files.remove({'_id' => file['_id']})
102
+ @chunks.remove({'files_id' => file['_id']})
103
+ end
104
+ end
105
+ alias_method :unlink, :delete
106
+
107
+ private
108
+
109
+ def default_grid_io_opts(filename=nil)
110
+ {:fs_name => @fs_name, :query => {'filename' => filename}, :query_opts => @default_query_opts}
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,339 @@
1
+ # --
2
+ # Copyright (C) 2008-2010 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 'digest/md5'
18
+ begin
19
+ require 'mime/types'
20
+ rescue LoadError
21
+ end
22
+
23
+ module Mongo
24
+
25
+ # GridIO objects represent files in the GridFS specification. This class
26
+ # manages the reading and writing of file chunks and metadata.
27
+ class GridIO
28
+ DEFAULT_CHUNK_SIZE = 256 * 1024
29
+ DEFAULT_CONTENT_TYPE = 'binary/octet-stream'
30
+
31
+ attr_reader :content_type, :chunk_size, :upload_date, :files_id, :filename,
32
+ :metadata, :server_md5, :client_md5, :file_length
33
+
34
+ # Create a new GridIO object. Note that most users will not need to use this class directly;
35
+ # the Grid and GridFileSystem classes will instantiate this class
36
+ #
37
+ # @param [Mongo::Collection] files a collection for storing file metadata.
38
+ # @param [Mongo::Collection] chunks a collection for storing file chunks.
39
+ # @param [String] filename the name of the file to open or write.
40
+ # @param [String] mode 'r' or 'w' or reading or creating a file.
41
+ #
42
+ # @option opts [Hash] :query a query selector used when opening the file in 'r' mode.
43
+ # @option opts [Hash] :query_opts any query options to be used when opening the file in 'r' mode.
44
+ # @option opts [String] :fs_name the file system prefix.
45
+ # @options opts [Integer] (262144) :chunk_size size of file chunks in bytes.
46
+ # @options opts [Hash] :metadata ({}) any additional data to store with the file.
47
+ # @options opts [ObjectID] :_id (ObjectID) a unique id for
48
+ # the file to be use in lieu of an automatically generated one.
49
+ # @options opts [String] :content_type ('binary/octet-stream') If no content type is specified,
50
+ # the content type will may be inferred from the filename extension if the mime-types gem can be
51
+ # loaded. Otherwise, the content type 'binary/octet-stream' will be used.
52
+ # @options opts [Boolean] :safe (false) When safe mode is enabled, the chunks sent to the server
53
+ # will be validated using an md5 hash. If validation fails, an exception will be raised.
54
+ def initialize(files, chunks, filename, mode, opts={})
55
+ @files = files
56
+ @chunks = chunks
57
+ @filename = filename
58
+ @mode = mode
59
+ @query = opts[:query] || {}
60
+ @query_opts = opts[:query_opts] || {}
61
+ @fs_name = opts[:fs_name] || Grid::DEFAULT_FS_NAME
62
+ @safe = opts[:safe] || false
63
+ @local_md5 = Digest::MD5.new if @safe
64
+
65
+ case @mode
66
+ when 'r' then init_read
67
+ when 'w' then init_write(opts)
68
+ else
69
+ raise GridError, "Invalid file mode #{@mode}. Mode should be 'r' or 'w'."
70
+ end
71
+ end
72
+
73
+ # Read the data from the file. If a length if specified, will read from the
74
+ # current file position.
75
+ #
76
+ # @param [Integer] length
77
+ #
78
+ # @return [String]
79
+ # the data in the file
80
+ def read(length=nil)
81
+ if length == 0
82
+ return ''
83
+ elsif length.nil? && @file_position.zero?
84
+ read_all
85
+ else
86
+ read_length(length)
87
+ end
88
+ end
89
+ alias_method :data, :read
90
+
91
+ # Write the given string (binary) data to the file.
92
+ #
93
+ # @param [String] string
94
+ # the data to write
95
+ #
96
+ # @return [Integer]
97
+ # the number of bytes written.
98
+ def write(io)
99
+ raise GridError, "file not opened for write" unless @mode[0] == ?w
100
+ if io.is_a? String
101
+ if @safe
102
+ @local_md5.update(io)
103
+ end
104
+ write_string(io)
105
+ else
106
+ length = 0
107
+ if @safe
108
+ while(string = io.read(@chunk_size))
109
+ @local_md5.update(string)
110
+ length += write_string(string)
111
+ end
112
+ else
113
+ while(string = io.read(@chunk_size))
114
+ length += write_string(string)
115
+ end
116
+ end
117
+ length
118
+ end
119
+ end
120
+
121
+ # Position the file pointer at the provided location.
122
+ #
123
+ # @param [Integer] pos
124
+ # the number of bytes to advance the file pointer. this can be a negative
125
+ # number.
126
+ # @param [Integer] whence
127
+ # one of IO::SEEK_CUR, IO::SEEK_END, or IO::SEEK_SET
128
+ #
129
+ # @return [Integer] the new file position
130
+ def seek(pos, whence=IO::SEEK_SET)
131
+ raise GridError, "Seek is only allowed in read mode." unless @mode == 'r'
132
+ target_pos = case whence
133
+ when IO::SEEK_CUR
134
+ @file_position + pos
135
+ when IO::SEEK_END
136
+ @file_length + pos
137
+ when IO::SEEK_SET
138
+ pos
139
+ end
140
+
141
+ new_chunk_number = (target_pos / @chunk_size).to_i
142
+ if new_chunk_number != @current_chunk['n']
143
+ save_chunk(@current_chunk) if @mode[0] == ?w
144
+ @current_chunk = get_chunk(new_chunk_number)
145
+ end
146
+ @file_position = target_pos
147
+ @chunk_position = @file_position % @chunk_size
148
+ @file_position
149
+ end
150
+
151
+ # The current position of the file.
152
+ #
153
+ # @return [Integer]
154
+ def tell
155
+ @file_position
156
+ end
157
+
158
+ # Creates or updates the document from the files collection that
159
+ # stores the chunks' metadata. The file becomes available only after
160
+ # this method has been called.
161
+ #
162
+ # This method will be invoked automatically when
163
+ # on GridIO#open is passed a block. Otherwise, it must be called manually.
164
+ #
165
+ # @return [True]
166
+ def close
167
+ if @mode[0] == ?w
168
+ @upload_date = Time.now.utc
169
+ @files.insert(to_mongo_object)
170
+ end
171
+ true
172
+ end
173
+
174
+ def inspect
175
+ "#<GridIO _id: #{@files_id}>"
176
+ end
177
+
178
+ private
179
+
180
+ def create_chunk(n)
181
+ chunk = OrderedHash.new
182
+ chunk['_id'] = Mongo::ObjectID.new
183
+ chunk['n'] = n
184
+ chunk['files_id'] = @files_id
185
+ chunk['data'] = ''
186
+ @chunk_position = 0
187
+ chunk
188
+ end
189
+
190
+ def save_chunk(chunk)
191
+ @chunks.insert(chunk)
192
+ end
193
+
194
+ def get_chunk(n)
195
+ chunk = @chunks.find({'files_id' => @files_id, 'n' => n}).next_document
196
+ @chunk_position = 0
197
+ chunk
198
+ end
199
+
200
+ def last_chunk_number
201
+ (@file_length / @chunk_size).to_i
202
+ end
203
+
204
+ # Read a file in its entirety.
205
+ def read_all
206
+ buf = ''
207
+ while true
208
+ buf << @current_chunk['data'].to_s
209
+ @current_chunk = get_chunk(@current_chunk['n'] + 1)
210
+ break unless @current_chunk
211
+ end
212
+ buf
213
+ end
214
+
215
+ # Read a file incrementally.
216
+ def read_length(length)
217
+ cache_chunk_data
218
+ remaining = (@file_length - @file_position)
219
+ to_read = length > remaining ? remaining : length
220
+ return nil unless remaining > 0
221
+
222
+ buf = ''
223
+ while to_read > 0
224
+ if @chunk_position == @chunk_data_length
225
+ @current_chunk = get_chunk(@current_chunk['n'] + 1)
226
+ cache_chunk_data
227
+ end
228
+ chunk_remainder = @chunk_data_length - @chunk_position
229
+ size = (to_read >= chunk_remainder) ? chunk_remainder : to_read
230
+ buf << @current_chunk_data[@chunk_position, size]
231
+ to_read -= size
232
+ @chunk_position += size
233
+ @file_position += size
234
+ end
235
+ buf
236
+ end
237
+
238
+ def cache_chunk_data
239
+ @current_chunk_data = @current_chunk['data'].to_s
240
+ if @current_chunk_data.respond_to?(:force_encoding)
241
+ @current_chunk_data.force_encoding("binary")
242
+ end
243
+ @chunk_data_length = @current_chunk['data'].length
244
+ end
245
+
246
+ def write_string(string)
247
+ # Since Ruby 1.9.1 doesn't necessarily store one character per byte.
248
+ if string.respond_to?(:force_encoding)
249
+ string.force_encoding("binary")
250
+ end
251
+
252
+ to_write = string.length
253
+ while (to_write > 0) do
254
+ if @current_chunk && @chunk_position == @chunk_size
255
+ next_chunk_number = @current_chunk['n'] + 1
256
+ @current_chunk = create_chunk(next_chunk_number)
257
+ end
258
+ chunk_available = @chunk_size - @chunk_position
259
+ step_size = (to_write > chunk_available) ? chunk_available : to_write
260
+ @current_chunk['data'] = Binary.new((@current_chunk['data'].to_s << string[-to_write, step_size]).unpack("c*"))
261
+ @chunk_position += step_size
262
+ to_write -= step_size
263
+ save_chunk(@current_chunk)
264
+ end
265
+ string.length - to_write
266
+ end
267
+
268
+ # Initialize the class for reading a file.
269
+ def init_read
270
+ doc = @files.find(@query, @query_opts).next_document
271
+ raise GridError, "Could not open file matching #{@query.inspect} #{@query_opts.inspect}" unless doc
272
+
273
+ @files_id = doc['_id']
274
+ @content_type = doc['contentType']
275
+ @chunk_size = doc['chunkSize']
276
+ @upload_date = doc['uploadDate']
277
+ @aliases = doc['aliases']
278
+ @file_length = doc['length']
279
+ @metadata = doc['metadata']
280
+ @md5 = doc['md5']
281
+ @filename = doc['filename']
282
+
283
+ @current_chunk = get_chunk(0)
284
+ @file_position = 0
285
+ end
286
+
287
+ # Initialize the class for writing a file.
288
+ def init_write(opts)
289
+ @files_id = opts[:_id] || Mongo::ObjectID.new
290
+ @content_type = opts[:content_type] || (defined? MIME) && get_content_type || DEFAULT_CONTENT_TYPE
291
+ @chunk_size = opts[:chunk_size] || DEFAULT_CHUNK_SIZE
292
+ @metadata = opts[:metadata] if opts[:metadata]
293
+ @aliases = opts[:aliases] if opts[:aliases]
294
+ @file_length = 0
295
+
296
+ @current_chunk = create_chunk(0)
297
+ @file_position = 0
298
+ end
299
+
300
+ def to_mongo_object
301
+ h = OrderedHash.new
302
+ h['_id'] = @files_id
303
+ h['filename'] = @filename
304
+ h['contentType'] = @content_type
305
+ h['length'] = @current_chunk ? @current_chunk['n'] * @chunk_size + @chunk_position : 0
306
+ h['chunkSize'] = @chunk_size
307
+ h['uploadDate'] = @upload_date
308
+ h['aliases'] = @aliases
309
+ h['metadata'] = @metadata
310
+ h['md5'] = get_md5
311
+ h
312
+ end
313
+
314
+ # Get a server-side md5 and validate against the client if running in safe mode.
315
+ def get_md5
316
+ md5_command = OrderedHash.new
317
+ md5_command['filemd5'] = @files_id
318
+ md5_command['root'] = @fs_name
319
+ @server_md5 = @files.db.command(md5_command)['md5']
320
+ if @safe
321
+ @client_md5 = @local_md5.hexdigest
322
+ if @local_md5 != @server_md5
323
+ raise GridError, "File on server failed MD5 check"
324
+ end
325
+ else
326
+ @server_md5
327
+ end
328
+ end
329
+
330
+ # Determine the content type based on the filename.
331
+ def get_content_type
332
+ if @filename
333
+ if types = MIME::Types.type_for(@filename)
334
+ types.first.simplified unless types.empty?
335
+ end
336
+ end
337
+ end
338
+ end
339
+ end