mongo 0.18.3 → 0.19

Sign up to get free protection for your applications and to get access to all the features.
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