mongo 0.18.3 → 0.19
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +41 -50
- data/Rakefile +14 -4
- data/examples/gridfs.rb +25 -70
- data/lib/mongo.rb +4 -2
- data/lib/mongo/collection.rb +70 -89
- data/lib/mongo/connection.rb +203 -43
- data/lib/mongo/cursor.rb +7 -7
- data/lib/mongo/db.rb +61 -18
- data/lib/mongo/exceptions.rb +7 -1
- data/lib/mongo/gridfs.rb +8 -1
- data/lib/mongo/gridfs/chunk.rb +2 -1
- data/lib/mongo/gridfs/grid.rb +90 -0
- data/lib/mongo/gridfs/grid_file_system.rb +113 -0
- data/lib/mongo/gridfs/grid_io.rb +339 -0
- data/lib/mongo/gridfs/grid_store.rb +43 -18
- data/lib/mongo/types/binary.rb +5 -1
- data/lib/mongo/types/code.rb +1 -1
- data/lib/mongo/types/dbref.rb +3 -1
- data/lib/mongo/types/min_max_keys.rb +1 -1
- data/lib/mongo/types/objectid.rb +16 -55
- data/lib/mongo/types/regexp_of_holding.rb +1 -1
- data/lib/mongo/util/bson_c.rb +2 -2
- data/lib/mongo/util/bson_ruby.rb +22 -11
- data/lib/mongo/util/byte_buffer.rb +1 -1
- data/lib/mongo/util/conversions.rb +1 -1
- data/lib/mongo/util/ordered_hash.rb +6 -1
- data/lib/mongo/util/server_version.rb +1 -1
- data/lib/mongo/util/support.rb +1 -1
- data/mongo-ruby-driver.gemspec +1 -1
- data/test/auxillary/authentication_test.rb +68 -0
- data/test/auxillary/autoreconnect_test.rb +41 -0
- data/test/binary_test.rb +15 -0
- data/test/{test_bson.rb → bson_test.rb} +63 -6
- data/test/{test_byte_buffer.rb → byte_buffer_test.rb} +0 -0
- data/test/{test_chunk.rb → chunk_test.rb} +0 -0
- data/test/{test_collection.rb → collection_test.rb} +35 -39
- data/test/{test_connection.rb → connection_test.rb} +33 -3
- data/test/{test_conversions.rb → conversions_test.rb} +0 -0
- data/test/{test_cursor.rb → cursor_test.rb} +0 -7
- data/test/{test_db_api.rb → db_api_test.rb} +3 -6
- data/test/{test_db_connection.rb → db_connection_test.rb} +0 -0
- data/test/{test_db.rb → db_test.rb} +33 -15
- data/test/grid_file_system_test.rb +210 -0
- data/test/grid_io_test.rb +78 -0
- data/test/{test_grid_store.rb → grid_store_test.rb} +33 -2
- data/test/grid_test.rb +87 -0
- data/test/{test_objectid.rb → objectid_test.rb} +2 -33
- data/test/{test_ordered_hash.rb → ordered_hash_test.rb} +4 -0
- data/test/{test_slave_connection.rb → slave_connection_test.rb} +0 -0
- data/test/test_helper.rb +2 -2
- data/test/{test_threading.rb → threading_test.rb} +0 -0
- data/test/unit/collection_test.rb +12 -3
- data/test/unit/connection_test.rb +85 -24
- data/test/unit/cursor_test.rb +1 -2
- data/test/unit/db_test.rb +70 -69
- metadata +27 -23
- data/bin/objectid_benchmark.rb +0 -23
- data/bin/perf.rb +0 -30
- data/lib/mongo/admin.rb +0 -95
- data/lib/mongo/util/xml_to_ruby.rb +0 -112
- data/test/test_admin.rb +0 -67
- data/test/test_round_trip.rb +0 -114
data/lib/mongo/exceptions.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# --
|
2
|
-
# Copyright (C) 2008-
|
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-
|
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
|
data/lib/mongo/gridfs/chunk.rb
CHANGED
@@ -1,5 +1,5 @@
|
|
1
1
|
# --
|
2
|
-
# Copyright (C) 2008-
|
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
|