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.
- 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
|