mogilefs-client 1.2.1 → 1.3.0
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/History.txt +18 -0
- data/Manifest.txt +2 -0
- data/Rakefile +5 -2
- data/bin/mog +201 -0
- data/lib/mogilefs.rb +1 -1
- data/lib/mogilefs/backend.rb +1 -0
- data/lib/mogilefs/httpfile.rb +22 -12
- data/lib/mogilefs/mogilefs.rb +72 -23
- data/lib/mogilefs/util.rb +30 -0
- data/test/setup.rb +102 -3
- data/test/test_backend.rb +3 -21
- data/test/test_mogilefs.rb +175 -7
- metadata +70 -55
data/History.txt
CHANGED
@@ -1,3 +1,21 @@
|
|
1
|
+
= 1.3.0
|
2
|
+
|
3
|
+
* Fixed MogileFS#rename. Bug #14465 submitted by Justin Dossey.
|
4
|
+
* Removed infinite loop in MogileFS::HTTPFile#store_file. Patch #13789
|
5
|
+
submitted by Andy Lo-A-Foe.
|
6
|
+
* Made MogileFS#get_file_data timeout configurable. Bug #13490 submitted by
|
7
|
+
Andy Lo-A-Foe.
|
8
|
+
* Add MogileFS#size. Feature Request #14484 submitted by Justin Dossey.
|
9
|
+
* Fix MogileFS#get_file_data to return the data for HTTP mode. Bug #7133
|
10
|
+
submitted by John Wanko.
|
11
|
+
* New maintainer: Eric Wong
|
12
|
+
* Add `mog' command-line tool as a demo/example
|
13
|
+
* Lower memory consumption with large files
|
14
|
+
* Allow get_file_data to accept a block for large files
|
15
|
+
* Fix each_keys loop termination condition
|
16
|
+
* Apply error handling patch from Matthew Willson. Bug #15987
|
17
|
+
* Merge large file patch from Andy Lo-A-Foe. Bug #13764
|
18
|
+
|
1
19
|
= 1.2.1
|
2
20
|
|
3
21
|
* Switched to Hoe.
|
data/Manifest.txt
CHANGED
@@ -3,6 +3,7 @@ LICENSE.txt
|
|
3
3
|
Manifest.txt
|
4
4
|
README.txt
|
5
5
|
Rakefile
|
6
|
+
bin/mog
|
6
7
|
lib/mogilefs.rb
|
7
8
|
lib/mogilefs/admin.rb
|
8
9
|
lib/mogilefs/backend.rb
|
@@ -11,6 +12,7 @@ lib/mogilefs/httpfile.rb
|
|
11
12
|
lib/mogilefs/mogilefs.rb
|
12
13
|
lib/mogilefs/nfsfile.rb
|
13
14
|
lib/mogilefs/pool.rb
|
15
|
+
lib/mogilefs/util.rb
|
14
16
|
test/setup.rb
|
15
17
|
test/test_admin.rb
|
16
18
|
test/test_backend.rb
|
data/Rakefile
CHANGED
@@ -6,12 +6,15 @@ require 'mogilefs'
|
|
6
6
|
|
7
7
|
Hoe.new 'mogilefs-client', MogileFS::VERSION do |p|
|
8
8
|
p.rubyforge_name = 'seattlerb'
|
9
|
-
p.author = 'Eric Hodel'
|
10
|
-
p.email = '
|
9
|
+
p.author = [ 'Eric Wong', 'Eric Hodel' ]
|
10
|
+
p.email = 'normalperson@yhbt.net' # (Eric Wong)
|
11
|
+
# p.email = 'drbrain@segment7.net' # (Eric Hodel)
|
11
12
|
p.summary = p.paragraphs_of('README.txt', 1).first
|
12
13
|
p.description = p.paragraphs_of('README.txt', 9).first
|
13
14
|
p.url = p.paragraphs_of('README.txt', 5).first
|
14
15
|
p.changes = p.paragraphs_of('History.txt', 0..1).join("\n\n")
|
16
|
+
|
17
|
+
p.extra_dev_deps << ['ZenTest', '>= 3.6.1']
|
15
18
|
end
|
16
19
|
|
17
20
|
# vim: syntax=Ruby
|
data/bin/mog
ADDED
@@ -0,0 +1,201 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
require 'mogilefs'
|
3
|
+
require 'optparse'
|
4
|
+
|
5
|
+
trap('INT') { exit 130 }
|
6
|
+
trap('PIPE') { exit 0 }
|
7
|
+
|
8
|
+
# this is to be compatible with config files used by the Perl tools
|
9
|
+
def parse_config_file!(path, overwrite = false)
|
10
|
+
dest = {}
|
11
|
+
File.open(path).each_line do |line|
|
12
|
+
line.strip!
|
13
|
+
if /^(domain|class)\s*=\s*(\S+)/.match(line)
|
14
|
+
dest[$1.to_sym] = $2
|
15
|
+
elsif m = /^(?:trackers|hosts)\s*=\s*(.*)/.match(line)
|
16
|
+
dest[:hosts] = $1.split(/\s*,\s*/)
|
17
|
+
else
|
18
|
+
STDERR.puts "Ignored configuration line: #{line}" unless /^#/.match(line)
|
19
|
+
end
|
20
|
+
end
|
21
|
+
dest
|
22
|
+
end
|
23
|
+
|
24
|
+
# parse the default config file if one exists
|
25
|
+
def_file = File.expand_path("~/.mogilefs-client.conf")
|
26
|
+
def_cfg = File.exist?(def_file) ? parse_config_file!(def_file) : {}
|
27
|
+
|
28
|
+
# parse the command-line first, these options take precedence over all else
|
29
|
+
cli_cfg = {}
|
30
|
+
config_file = nil
|
31
|
+
ls_l = false
|
32
|
+
ls_h = false
|
33
|
+
test = {}
|
34
|
+
|
35
|
+
ARGV.options do |x|
|
36
|
+
x.banner = "Usage: #{$0} [options] <command> [<arguments>]"
|
37
|
+
x.separator ''
|
38
|
+
|
39
|
+
x.on('-c', '--config=/path/to/config',
|
40
|
+
'config file to load') { |file| config_file = file }
|
41
|
+
|
42
|
+
x.on('-t', '--trackers=host1[,host2]', '--hosts=host1[,host2]', Array,
|
43
|
+
'hostnames/IP addresses of trackers') do |trackers|
|
44
|
+
cli_cfg[:hosts] = trackers
|
45
|
+
end
|
46
|
+
|
47
|
+
x.on('-e', 'True if key exists') { test[:e] = true }
|
48
|
+
|
49
|
+
x.on('-C', '--class=s', 'class') { |klass| cli_cfg[:class] = klass }
|
50
|
+
x.on('-d', '--domain=s', 'domain') { |domain| cli_cfg[:domain] = domain }
|
51
|
+
x.on('-l', "long listing format (`ls' command)") { ls_l = true }
|
52
|
+
x.on('-h', '--human-readable',
|
53
|
+
"print sizes in human-readable format (`ls' command)") { ls_h = true }
|
54
|
+
|
55
|
+
x.separator ''
|
56
|
+
x.on('--help', 'Show this help message.') { puts x; exit }
|
57
|
+
x.parse!
|
58
|
+
end
|
59
|
+
|
60
|
+
# parse the config file specified at the command-line
|
61
|
+
file_cfg = config_file ? parse_config_file!(config_file, true) : {}
|
62
|
+
|
63
|
+
# read environment variables, too. This Ruby API favors the term
|
64
|
+
# "hosts", however upstream MogileFS teminology favors "trackers" instead.
|
65
|
+
# Favor the term more consistent with what the MogileFS inventors used.
|
66
|
+
env_cfg = {}
|
67
|
+
if ENV["MOG_TRACKERS"]
|
68
|
+
env_cfg[:hosts] = ENV["MOG_TRACKERS"].split(/\s*,\s*/)
|
69
|
+
end
|
70
|
+
if ENV["MOG_HOSTS"] && env_cfg[:hosts].empty?
|
71
|
+
env_cfg[:hosts] = ENV["MOG_HOSTS"].split(/\s*,\s*/)
|
72
|
+
end
|
73
|
+
env_cfg[:domain] = ENV["MOG_DOMAIN"] if ENV["MOG_DOMAIN"]
|
74
|
+
env_cfg[:class] = ENV["MOG_CLASS"] if ENV["MOG_CLASS"]
|
75
|
+
|
76
|
+
# merge the configs, favoring them in order specified:
|
77
|
+
cfg = {}.merge(def_cfg).merge(env_cfg).merge(file_cfg).merge(cli_cfg)
|
78
|
+
|
79
|
+
# error-checking
|
80
|
+
err = []
|
81
|
+
err << "trackers must be specified" if cfg[:hosts].nil? || cfg[:hosts].empty?
|
82
|
+
err << "domain must be specified" unless cfg[:domain]
|
83
|
+
if err.any?
|
84
|
+
STDERR.puts "Errors:\n #{err.join("\n ")}"
|
85
|
+
STDERR.puts ARGV.options
|
86
|
+
exit 1
|
87
|
+
end
|
88
|
+
|
89
|
+
unless cmd = ARGV.shift
|
90
|
+
STDERR.puts ARGV.options
|
91
|
+
exit 1
|
92
|
+
end
|
93
|
+
|
94
|
+
include MogileFS::Util
|
95
|
+
mg = MogileFS::MogileFS.new(cfg)
|
96
|
+
|
97
|
+
begin
|
98
|
+
case cmd
|
99
|
+
when 'cp'
|
100
|
+
filename = ARGV.shift or raise ArgumentError, '<filename> <key>'
|
101
|
+
key = ARGV.shift or raise ArgumentError, '<filename> <key>'
|
102
|
+
ARGV.shift and raise ArgumentError, '<filename> <key>'
|
103
|
+
cfg[:class] or raise ArgumentError, 'E: --class must be specified'
|
104
|
+
mg.store_file(key, cfg[:class], filename)
|
105
|
+
when 'cat'
|
106
|
+
ARGV.empty? and raise ArgumentError, '<key1> [<key2> ...]'
|
107
|
+
ARGV.each { |key| mg.get_file_data(key) { |fp| sysrwloop(fp, STDOUT) } }
|
108
|
+
when 'ls'
|
109
|
+
prefixes = ARGV.empty? ? [ nil ] : ARGV
|
110
|
+
prefixes.each do |prefix|
|
111
|
+
mg.each_key(prefix) do |key|
|
112
|
+
if ls_l
|
113
|
+
path_nr = "% 2d" % mg.get_paths(key).size
|
114
|
+
size = mg.size(key)
|
115
|
+
if ls_h && size > 1024
|
116
|
+
suff = ''
|
117
|
+
%w(K M G).each do |s|
|
118
|
+
size /= 1024.0
|
119
|
+
suff = s
|
120
|
+
break if size <= 1024
|
121
|
+
end
|
122
|
+
size = sprintf("%.1f%s", size, suff)
|
123
|
+
else
|
124
|
+
size = size.to_s
|
125
|
+
end
|
126
|
+
size = (' ' * (12 - size.length)) << size # right justify
|
127
|
+
puts [ path_nr, size, key ].pack("A4 A16 A32")
|
128
|
+
else
|
129
|
+
puts key
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
when 'rm'
|
134
|
+
ARGV.empty? and raise ArgumentError, '<key1> [<key2>]'
|
135
|
+
ARGV.each { |key| mg.delete(key) }
|
136
|
+
when 'mv'
|
137
|
+
from = ARGV.shift or raise ArgumentError, '<from> <to>'
|
138
|
+
to = ARGV.shift or raise ArgumentError, '<from> <to>'
|
139
|
+
ARGV.shift and raise ArgumentError, '<from> <to>'
|
140
|
+
mg.rename(from, to)
|
141
|
+
when 'stat' # this outputs a RFC822-like format
|
142
|
+
ARGV.empty? and raise ArgumentError, '<key1> [<key2>]'
|
143
|
+
ARGV.each_with_index do |key, i|
|
144
|
+
if size = mg.size(key)
|
145
|
+
puts "Key: #{key}"
|
146
|
+
puts "Size: #{size}"
|
147
|
+
mg.get_paths(key).each_with_index do |path,i|
|
148
|
+
puts "URL-#{i}: #{path}"
|
149
|
+
end
|
150
|
+
puts ""
|
151
|
+
else
|
152
|
+
STDERR.puts "No such key: #{key}"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
when 'tee'
|
156
|
+
require 'tempfile'
|
157
|
+
key = ARGV.shift or raise ArgumentError, '<key>'
|
158
|
+
ARGV.shift and raise ArgumentError, '<key>'
|
159
|
+
cfg[:class] or raise ArgumentError, 'E: --class must be specified'
|
160
|
+
buf = ''
|
161
|
+
tmp = Tempfile.new('mog-tee') # TODO: explore Transfer-Encoding:chunked :)
|
162
|
+
at_exit { tmp.unlink }
|
163
|
+
begin
|
164
|
+
sysrwloop(STDIN, tmp)
|
165
|
+
mg.store_file(key, cfg[:class], tmp.path)
|
166
|
+
ensure
|
167
|
+
tmp.close
|
168
|
+
end
|
169
|
+
when 'test'
|
170
|
+
truth, ok = true, nil
|
171
|
+
raise ArgumentError, "-e must be specified" unless (test.size == 1)
|
172
|
+
|
173
|
+
truth, key = case ARGV.size
|
174
|
+
when 1
|
175
|
+
[ true, ARGV[0] ]
|
176
|
+
when 2
|
177
|
+
if ARGV[0] != "!"
|
178
|
+
raise ArgumentError, "#{ARGV[0]}: binary operator expected"
|
179
|
+
end
|
180
|
+
[ false, ARGV[1] ]
|
181
|
+
else
|
182
|
+
raise ArgumentError, "Too many arguments"
|
183
|
+
end
|
184
|
+
|
185
|
+
paths = mg.get_paths(key)
|
186
|
+
if test[:e]
|
187
|
+
ok = !!(paths && paths.size > 0)
|
188
|
+
else
|
189
|
+
raise ArgumentError, "Unknown flag: -#{test.keys.first}"
|
190
|
+
end
|
191
|
+
|
192
|
+
truth or ok = ! ok
|
193
|
+
exit ok ? 0 : 1
|
194
|
+
else
|
195
|
+
raise ArgumentError, "Unknown command: #{cmd}"
|
196
|
+
end
|
197
|
+
rescue ArgumentError => err
|
198
|
+
STDERR.puts "Usage: #{$0} #{cmd} #{err.message}"
|
199
|
+
exit 1
|
200
|
+
end
|
201
|
+
exit 0
|
data/lib/mogilefs.rb
CHANGED
data/lib/mogilefs/backend.rb
CHANGED
@@ -155,6 +155,7 @@ class MogileFS::Backend
|
|
155
155
|
found = select [socket], nil, nil, @timeout
|
156
156
|
if found.nil? or found.empty? then
|
157
157
|
peer = (@socket ? "#{@socket.peeraddr[3]}:#{@socket.peeraddr[1]} " : nil)
|
158
|
+
socket.close # we DO NOT want the response we timed out waiting for, to crop up later on, on the same socket, intersperesed with a subsequent request! so, we close the socket if it times out like this
|
158
159
|
raise MogileFS::UnreadableSocketError, "#{peer}never became readable"
|
159
160
|
end
|
160
161
|
return true
|
data/lib/mogilefs/httpfile.rb
CHANGED
@@ -3,6 +3,7 @@ require 'socket'
|
|
3
3
|
require 'stringio'
|
4
4
|
require 'uri'
|
5
5
|
require 'mogilefs/backend'
|
6
|
+
require 'mogilefs/util'
|
6
7
|
|
7
8
|
##
|
8
9
|
# HTTPFile wraps up the new file operations for storing files onto an HTTP
|
@@ -17,6 +18,7 @@ require 'mogilefs/backend'
|
|
17
18
|
# TODO dup'd content in MogileFS::NFSFile
|
18
19
|
|
19
20
|
class MogileFS::HTTPFile < StringIO
|
21
|
+
include MogileFS::Util
|
20
22
|
|
21
23
|
##
|
22
24
|
# The path this file will be stored to.
|
@@ -34,6 +36,11 @@ class MogileFS::HTTPFile < StringIO
|
|
34
36
|
|
35
37
|
attr_reader :class
|
36
38
|
|
39
|
+
##
|
40
|
+
# The bigfile name in case we have file > 256M
|
41
|
+
|
42
|
+
attr_accessor :bigfile
|
43
|
+
|
37
44
|
##
|
38
45
|
# Works like File.open. Use MogileFS::MogileFS#new_file instead of this
|
39
46
|
# method.
|
@@ -62,6 +69,7 @@ class MogileFS::HTTPFile < StringIO
|
|
62
69
|
@devid = devid
|
63
70
|
@klass = klass
|
64
71
|
@key = key
|
72
|
+
@bigfile = nil
|
65
73
|
|
66
74
|
@dests = dests.map { |(_,u)| URI.parse u }
|
67
75
|
@tried = {}
|
@@ -75,7 +83,17 @@ class MogileFS::HTTPFile < StringIO
|
|
75
83
|
def close
|
76
84
|
connect_socket
|
77
85
|
|
78
|
-
|
86
|
+
file_size = nil
|
87
|
+
if @bigfile
|
88
|
+
# Don't try to run out of memory
|
89
|
+
fp = File.open(@bigfile)
|
90
|
+
file_size = File.size(@bigfile)
|
91
|
+
@socket.write "PUT #{@path.request_uri} HTTP/1.0\r\nContent-Length: #{file_size}\r\n\r\n"
|
92
|
+
sysrwloop(fp, @socket)
|
93
|
+
fp.close
|
94
|
+
else
|
95
|
+
@socket.write "PUT #{@path.request_uri} HTTP/1.0\r\nContent-Length: #{length}\r\n\r\n#{string}"
|
96
|
+
end
|
79
97
|
|
80
98
|
if connected? then
|
81
99
|
line = @socket.gets
|
@@ -86,27 +104,19 @@ class MogileFS::HTTPFile < StringIO
|
|
86
104
|
case status
|
87
105
|
when 200..299 then # success!
|
88
106
|
else
|
89
|
-
|
90
|
-
body = []
|
91
|
-
line = @socket.gets
|
92
|
-
until line.nil? do
|
93
|
-
line.strip
|
94
|
-
found_header = true if line.nil?
|
95
|
-
next unless found_header
|
96
|
-
body << " #{line}"
|
97
|
-
end
|
98
|
-
body = body[0, 512] if body.length > 512
|
99
|
-
raise "HTTP response status from upload: #{body}"
|
107
|
+
raise "HTTP response status from upload: #{status}"
|
100
108
|
end
|
101
109
|
else
|
102
110
|
raise "Response line not understood: #{line}"
|
103
111
|
end
|
112
|
+
|
104
113
|
@socket.close
|
105
114
|
end
|
106
115
|
|
107
116
|
@mg.backend.create_close(:fid => @fid, :devid => @devid,
|
108
117
|
:domain => @mg.domain, :key => @key,
|
109
118
|
:path => @path, :size => length)
|
119
|
+
return file_size if @bigfile
|
110
120
|
return nil
|
111
121
|
end
|
112
122
|
|
data/lib/mogilefs/mogilefs.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'open-uri'
|
2
|
+
require 'net/http'
|
2
3
|
require 'timeout'
|
3
4
|
|
4
5
|
require 'mogilefs/client'
|
@@ -24,6 +25,11 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
24
25
|
|
25
26
|
attr_reader :domain
|
26
27
|
|
28
|
+
##
|
29
|
+
# The timeout for get_file_data. Defaults to five seconds.
|
30
|
+
|
31
|
+
attr_accessor :get_file_data_timeout
|
32
|
+
|
27
33
|
##
|
28
34
|
# Creates a new MogileFS::MogileFS instance. +args+ must include a key
|
29
35
|
# :domain specifying the domain of this client. A key :root will be used to
|
@@ -33,6 +39,8 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
33
39
|
@domain = args[:domain]
|
34
40
|
@root = args[:root]
|
35
41
|
|
42
|
+
@get_file_data_timeout = 5
|
43
|
+
|
36
44
|
raise ArgumentError, "you must specify a domain" unless @domain
|
37
45
|
|
38
46
|
super
|
@@ -46,7 +54,7 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
46
54
|
|
47
55
|
keys, after = list_keys prefix
|
48
56
|
|
49
|
-
until keys.empty? do
|
57
|
+
until keys.nil? or keys.empty? do
|
50
58
|
keys.each { |k| yield k }
|
51
59
|
keys, after = list_keys prefix, after
|
52
60
|
end
|
@@ -57,7 +65,7 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
57
65
|
##
|
58
66
|
# Retrieves the contents of +key+.
|
59
67
|
|
60
|
-
def get_file_data(key)
|
68
|
+
def get_file_data(key, &block)
|
61
69
|
paths = get_paths key
|
62
70
|
|
63
71
|
return nil unless paths
|
@@ -68,8 +76,21 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
68
76
|
when /^http:\/\// then
|
69
77
|
begin
|
70
78
|
path = URI.parse path
|
71
|
-
|
72
|
-
|
79
|
+
|
80
|
+
if block_given?
|
81
|
+
sock = nil
|
82
|
+
timeout @get_file_data_timeout, MogileFS::Timeout do
|
83
|
+
sock = TCPSocket.new(path.host, path.port)
|
84
|
+
sock.sync = true
|
85
|
+
sock.syswrite("GET #{path.request_uri} HTTP/1.0\r\n\r\n")
|
86
|
+
buf = sock.recv(4096, Socket::MSG_PEEK)
|
87
|
+
head, body = buf.split(/\r\n\r\n/, 2)
|
88
|
+
head = sock.recv(head.size + 4)
|
89
|
+
end
|
90
|
+
return yield(sock)
|
91
|
+
else
|
92
|
+
return path.read
|
93
|
+
end
|
73
94
|
rescue MogileFS::Timeout
|
74
95
|
next
|
75
96
|
end
|
@@ -108,7 +129,7 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
108
129
|
res = @backend.create_open(:domain => @domain, :class => klass,
|
109
130
|
:key => key, :multi_dest => 1)
|
110
131
|
|
111
|
-
raise "#{@backend.lasterr}: #{@backend.lasterrstr}" if res.nil? # HACK
|
132
|
+
raise "#{@backend.lasterr}: #{@backend.lasterrstr}" if res.nil? || res == {} # HACK
|
112
133
|
|
113
134
|
dests = nil
|
114
135
|
|
@@ -130,6 +151,8 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
130
151
|
devid, path = dest
|
131
152
|
|
132
153
|
case path
|
154
|
+
when nil, '' then
|
155
|
+
raise 'Empty path for mogile upload'
|
133
156
|
when /^http:\/\// then
|
134
157
|
MogileFS::HTTPFile.open(self, res['fid'], path, devid, klass, key,
|
135
158
|
dests, bytes, &block)
|
@@ -146,10 +169,15 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
146
169
|
raise 'readonly mogilefs' if readonly?
|
147
170
|
|
148
171
|
new_file key, klass do |mfp|
|
149
|
-
if file.respond_to? :
|
150
|
-
return
|
172
|
+
if file.respond_to? :sysread then
|
173
|
+
return sysrwloop(file, mfp)
|
151
174
|
else
|
152
|
-
|
175
|
+
if File.size(file) > 0x10000 # Bigass file, handle differently
|
176
|
+
mfp.bigfile = file
|
177
|
+
return mfp.close
|
178
|
+
else
|
179
|
+
return File.open(file) { |fp| sysrwloop(fp, mfp) }
|
180
|
+
end
|
153
181
|
end
|
154
182
|
end
|
155
183
|
end
|
@@ -196,8 +224,43 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
196
224
|
res = @backend.rename :domain => @domain, :from_key => from, :to_key => to
|
197
225
|
|
198
226
|
if res.nil? and @backend.lasterr != 'unknown_key' then
|
199
|
-
raise "unable to rename #{
|
227
|
+
raise "unable to rename #{from} to #{to}: #{@backend.lasterr}"
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
##
|
232
|
+
# Returns the size of +key+.
|
233
|
+
def size(key)
|
234
|
+
paths = get_paths key
|
235
|
+
|
236
|
+
return nil unless paths
|
237
|
+
|
238
|
+
paths.each do |path|
|
239
|
+
next unless path
|
240
|
+
case path
|
241
|
+
when /^http:\/\// then
|
242
|
+
begin
|
243
|
+
url = URI.parse path
|
244
|
+
|
245
|
+
req = Net::HTTP::Head.new url.request_uri
|
246
|
+
|
247
|
+
res = timeout @get_file_data_timeout, MogileFS::Timeout do
|
248
|
+
Net::HTTP.start url.host, url.port do |http|
|
249
|
+
http.request req
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
return res['Content-Length'].to_i
|
254
|
+
rescue MogileFS::Timeout
|
255
|
+
next
|
256
|
+
end
|
257
|
+
else
|
258
|
+
next unless File.exist? path
|
259
|
+
return File.size(path)
|
260
|
+
end
|
200
261
|
end
|
262
|
+
|
263
|
+
return nil
|
201
264
|
end
|
202
265
|
|
203
266
|
##
|
@@ -215,19 +278,5 @@ class MogileFS::MogileFS < MogileFS::Client
|
|
215
278
|
return keys, res['next_after']
|
216
279
|
end
|
217
280
|
|
218
|
-
private
|
219
|
-
|
220
|
-
def copy(from, to) # HACK use FileUtils
|
221
|
-
bytes = 0
|
222
|
-
|
223
|
-
until from.eof? do
|
224
|
-
chunk = from.read 8192
|
225
|
-
to.write chunk
|
226
|
-
bytes += chunk.length
|
227
|
-
end
|
228
|
-
|
229
|
-
return bytes
|
230
|
-
end
|
231
|
-
|
232
281
|
end
|
233
282
|
|
@@ -0,0 +1,30 @@
|
|
1
|
+
module MogileFS::Util
|
2
|
+
|
3
|
+
CHUNK_SIZE = 65536
|
4
|
+
|
5
|
+
# for copying large files while avoiding GC thrashing as much as possible
|
6
|
+
def sysrwloop(io_rd, io_wr)
|
7
|
+
copied = 0
|
8
|
+
# avoid making sysread repeatedly allocate a new String
|
9
|
+
# This is not well-documented, but both read/sysread can take
|
10
|
+
# an optional second argument to use as the buffer to avoid
|
11
|
+
# GC overhead of creating new strings in a loop
|
12
|
+
buf = ' ' * CHUNK_SIZE # preallocate to avoid GC thrashing
|
13
|
+
io_wr.sync = true
|
14
|
+
loop do
|
15
|
+
begin
|
16
|
+
io_rd.sysread(CHUNK_SIZE, buf)
|
17
|
+
loop do
|
18
|
+
w = io_wr.syswrite(buf)
|
19
|
+
copied += w
|
20
|
+
break if w == buf.size
|
21
|
+
buf = buf[w..-1]
|
22
|
+
end
|
23
|
+
rescue EOFError
|
24
|
+
break
|
25
|
+
end
|
26
|
+
end
|
27
|
+
copied
|
28
|
+
end # sysrwloop
|
29
|
+
|
30
|
+
end
|
data/test/setup.rb
CHANGED
@@ -1,5 +1,12 @@
|
|
1
1
|
require 'test/unit'
|
2
2
|
|
3
|
+
require 'fileutils'
|
4
|
+
require 'tmpdir'
|
5
|
+
require 'stringio'
|
6
|
+
|
7
|
+
require 'rubygems'
|
8
|
+
require 'test/zentest_assertions'
|
9
|
+
|
3
10
|
$TESTING = true
|
4
11
|
|
5
12
|
require 'mogilefs'
|
@@ -32,22 +39,114 @@ class FakeBackend
|
|
32
39
|
|
33
40
|
end
|
34
41
|
|
42
|
+
class FakeSocket
|
43
|
+
|
44
|
+
attr_reader :read_s
|
45
|
+
attr_reader :write_s
|
46
|
+
attr_reader :sync
|
47
|
+
|
48
|
+
def initialize(read = '', write = StringIO.new)
|
49
|
+
@read_s = read.class.method_defined?(:sysread) ? read : StringIO.new(read)
|
50
|
+
@write_s = write
|
51
|
+
@closed = false
|
52
|
+
@sync = false
|
53
|
+
end
|
54
|
+
|
55
|
+
def sync=(do_sync)
|
56
|
+
@write_s.sync = do_sync
|
57
|
+
@read_s.sync = do_sync
|
58
|
+
end
|
59
|
+
|
60
|
+
def closed?
|
61
|
+
@closed
|
62
|
+
end
|
63
|
+
|
64
|
+
def close
|
65
|
+
@closed = true
|
66
|
+
return nil
|
67
|
+
end
|
68
|
+
|
69
|
+
def gets
|
70
|
+
@read_s.gets
|
71
|
+
end
|
72
|
+
|
73
|
+
def peeraddr
|
74
|
+
['AF_INET', 6001, 'localhost', '127.0.0.1']
|
75
|
+
end
|
76
|
+
|
77
|
+
def read(bytes)
|
78
|
+
@read_s.read bytes
|
79
|
+
end
|
80
|
+
|
81
|
+
def sysread(bytes, buf = '')
|
82
|
+
@read_s.sysread bytes, buf
|
83
|
+
end
|
84
|
+
|
85
|
+
def recv_nonblock(bytes, flags = 0)
|
86
|
+
ret = @read_s.sysread(bytes)
|
87
|
+
# Ruby doesn't expose pread(2)
|
88
|
+
if (flags & Socket::MSG_PEEK) != 0
|
89
|
+
@read_s.sysseek(-ret.size, IO::SEEK_CUR)
|
90
|
+
end
|
91
|
+
ret
|
92
|
+
end
|
93
|
+
alias_method :recv, :recv_nonblock
|
94
|
+
|
95
|
+
def write(data)
|
96
|
+
@write_s.write data
|
97
|
+
end
|
98
|
+
|
99
|
+
def syswrite(data)
|
100
|
+
@write_s.syswrite data
|
101
|
+
end
|
102
|
+
|
103
|
+
end
|
104
|
+
|
35
105
|
class MogileFS::Client
|
36
106
|
attr_writer :readonly
|
37
107
|
end
|
38
108
|
|
109
|
+
class TCPSocket
|
110
|
+
|
111
|
+
class << self
|
112
|
+
|
113
|
+
attr_accessor :connections
|
114
|
+
attr_accessor :sockets
|
115
|
+
|
116
|
+
alias old_new new
|
117
|
+
|
118
|
+
def new(host, port)
|
119
|
+
raise Errno::ECONNREFUSED if @sockets.empty?
|
120
|
+
@connections << [host, port]
|
121
|
+
@sockets.pop
|
122
|
+
end
|
123
|
+
|
124
|
+
alias open new
|
125
|
+
|
126
|
+
end
|
127
|
+
|
128
|
+
end
|
129
|
+
|
39
130
|
class TestMogileFS < Test::Unit::TestCase
|
40
131
|
|
132
|
+
undef_method :default_test
|
133
|
+
|
41
134
|
def setup
|
42
|
-
|
43
|
-
@root = '
|
135
|
+
@tempdir = File.join Dir.tmpdir, "test_mogilefs_#{$$}"
|
136
|
+
@root = File.join @tempdir, 'root'
|
137
|
+
FileUtils.mkdir_p @root
|
138
|
+
|
44
139
|
@client = @klass.new :hosts => ['kaa:6001'], :domain => 'test',
|
45
140
|
:root => @root
|
46
141
|
@backend = FakeBackend.new
|
47
142
|
@client.instance_variable_set '@backend', @backend
|
143
|
+
|
144
|
+
TCPSocket.sockets = []
|
145
|
+
TCPSocket.connections = []
|
48
146
|
end
|
49
147
|
|
50
|
-
def
|
148
|
+
def teardown
|
149
|
+
FileUtils.rm_rf @tempdir
|
51
150
|
end
|
52
151
|
|
53
152
|
end
|
data/test/test_backend.rb
CHANGED
@@ -1,4 +1,5 @@
|
|
1
1
|
require 'test/unit'
|
2
|
+
require 'test/setup'
|
2
3
|
|
3
4
|
$TESTING = true
|
4
5
|
|
@@ -12,30 +13,11 @@ class MogileFS::Backend
|
|
12
13
|
|
13
14
|
end
|
14
15
|
|
15
|
-
class FakeSocket
|
16
|
-
|
17
|
-
def initialize
|
18
|
-
@closed = false
|
19
|
-
end
|
20
|
-
|
21
|
-
def closed?
|
22
|
-
@closed
|
23
|
-
end
|
24
|
-
|
25
|
-
def close
|
26
|
-
@closed = true
|
27
|
-
return nil
|
28
|
-
end
|
29
|
-
|
30
|
-
def peeraddr
|
31
|
-
['AF_INET', 6001, 'localhost', '127.0.0.1']
|
32
|
-
end
|
33
|
-
|
34
|
-
end
|
35
|
-
|
36
16
|
class TestBackend < Test::Unit::TestCase
|
37
17
|
|
38
18
|
def setup
|
19
|
+
TCPSocket.connections = []
|
20
|
+
TCPSocket.sockets = []
|
39
21
|
@backend = MogileFS::Backend.new :hosts => ['localhost:1']
|
40
22
|
end
|
41
23
|
|
data/test/test_mogilefs.rb
CHANGED
@@ -1,15 +1,23 @@
|
|
1
1
|
require 'test/setup'
|
2
|
+
require 'stringio'
|
3
|
+
require 'tempfile'
|
4
|
+
require 'fileutils'
|
2
5
|
|
3
6
|
class URI::HTTP
|
4
7
|
|
5
8
|
class << self
|
6
9
|
attr_accessor :read_data
|
10
|
+
attr_accessor :open_data
|
7
11
|
end
|
8
12
|
|
9
13
|
def read
|
10
14
|
self.class.read_data.shift
|
11
15
|
end
|
12
16
|
|
17
|
+
def open(&block)
|
18
|
+
yield self.class.open_data
|
19
|
+
end
|
20
|
+
|
13
21
|
end
|
14
22
|
|
15
23
|
class TestMogileFS__MogileFS < TestMogileFS
|
@@ -21,7 +29,7 @@ class TestMogileFS__MogileFS < TestMogileFS
|
|
21
29
|
|
22
30
|
def test_initialize
|
23
31
|
assert_equal 'test', @client.domain
|
24
|
-
assert_equal
|
32
|
+
assert_equal @root, @client.root
|
25
33
|
|
26
34
|
assert_raises ArgumentError do
|
27
35
|
MogileFS::MogileFS.new :hosts => ['kaa:6001'], :root => '/mogilefs/test'
|
@@ -39,6 +47,43 @@ class TestMogileFS__MogileFS < TestMogileFS
|
|
39
47
|
assert_equal 'data!', @client.get_file_data('key')
|
40
48
|
end
|
41
49
|
|
50
|
+
def test_get_file_data_http_block
|
51
|
+
tmpfp = Tempfile.new('test_mogilefs.open_data')
|
52
|
+
nr = 100 # tested with 1000
|
53
|
+
chunk_size = 1024 * 1024
|
54
|
+
expect_size = nr * chunk_size
|
55
|
+
header = "HTTP/1.0 200 OK\r\n" \
|
56
|
+
"Content-Length: #{expect_size}\r\n\r\n"
|
57
|
+
assert_equal header.size, tmpfp.syswrite(header)
|
58
|
+
nr.times { assert_equal chunk_size, tmpfp.syswrite(' ' * chunk_size) }
|
59
|
+
assert_equal expect_size + header.size, File.size(tmpfp.path)
|
60
|
+
tmpfp.sysseek(0)
|
61
|
+
socket = FakeSocket.new(tmpfp)
|
62
|
+
TCPSocket.sockets << socket
|
63
|
+
|
64
|
+
path1 = 'http://rur-1/dev1/0/000/000/0000000062.fid'
|
65
|
+
path2 = 'http://rur-2/dev2/0/000/000/0000000062.fid'
|
66
|
+
|
67
|
+
@backend.get_paths = { 'paths' => 2, 'path1' => path1, 'path2' => path2 }
|
68
|
+
|
69
|
+
data = Tempfile.new('test_mogilefs.dest_data')
|
70
|
+
@client.get_file_data('key') do |fp|
|
71
|
+
buf = ''
|
72
|
+
read_nr = nr = 0
|
73
|
+
loop do
|
74
|
+
begin
|
75
|
+
fp.sysread(16384, buf)
|
76
|
+
read_nr = buf.size
|
77
|
+
nr += read_nr
|
78
|
+
assert_equal read_nr, data.syswrite(buf), "partial write"
|
79
|
+
rescue EOFError
|
80
|
+
break
|
81
|
+
end
|
82
|
+
end
|
83
|
+
assert_equal expect_size, nr, "size mismatch"
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
42
87
|
def test_get_paths
|
43
88
|
path1 = 'rur-1/dev1/0/000/000/0000000062.fid'
|
44
89
|
path2 = 'rur-2/dev2/0/000/000/0000000062.fid'
|
@@ -114,8 +159,120 @@ class TestMogileFS__MogileFS < TestMogileFS
|
|
114
159
|
end
|
115
160
|
end
|
116
161
|
|
162
|
+
def test_size_http
|
163
|
+
socket = FakeSocket.new <<-EOF
|
164
|
+
HTTP/1.0 200 OK\r
|
165
|
+
Content-Length: 5\r
|
166
|
+
EOF
|
167
|
+
|
168
|
+
TCPSocket.sockets << socket
|
169
|
+
|
170
|
+
path = 'http://example.com/path'
|
171
|
+
|
172
|
+
@backend.get_paths = { 'paths' => 1, 'path1' => path }
|
173
|
+
|
174
|
+
assert_equal 5, @client.size('key')
|
175
|
+
|
176
|
+
socket.write_s.rewind
|
177
|
+
|
178
|
+
assert_equal "HEAD /path HTTP/1.1\r\n", socket.write_s.gets
|
179
|
+
|
180
|
+
assert_equal ['example.com', 80], TCPSocket.connections.shift
|
181
|
+
assert_empty TCPSocket.connections
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_size_nfs
|
185
|
+
path = File.join @root, 'path'
|
186
|
+
|
187
|
+
File.open path, 'w' do |fp| fp.write 'data!' end
|
188
|
+
|
189
|
+
@backend.get_paths = { 'paths' => 1, 'path1' => 'path' }
|
190
|
+
|
191
|
+
assert_equal 5, @client.size('key')
|
192
|
+
end
|
193
|
+
|
194
|
+
def test_store_content_http
|
195
|
+
socket = FakeSocket.new 'HTTP/1.0 200 OK'
|
196
|
+
|
197
|
+
TCPSocket.sockets << socket
|
198
|
+
|
199
|
+
@backend.create_open = {
|
200
|
+
'devid' => '1',
|
201
|
+
'path' => 'http://example.com/path',
|
202
|
+
}
|
203
|
+
|
204
|
+
@client.store_content 'new_key', 'test', 'data'
|
205
|
+
|
206
|
+
expected = <<-EOF.chomp
|
207
|
+
PUT /path HTTP/1.0\r
|
208
|
+
Content-Length: 4\r
|
209
|
+
\r
|
210
|
+
data
|
211
|
+
EOF
|
212
|
+
|
213
|
+
assert_equal expected, socket.write_s.string
|
214
|
+
|
215
|
+
assert_equal ['example.com', 80], TCPSocket.connections.shift
|
216
|
+
assert_empty TCPSocket.connections
|
217
|
+
end
|
218
|
+
|
219
|
+
def test_store_content_http_empty
|
220
|
+
socket = FakeSocket.new 'HTTP/1.0 200 OK'
|
221
|
+
|
222
|
+
TCPSocket.sockets << socket
|
223
|
+
|
224
|
+
@backend.create_open = {
|
225
|
+
'devid' => '1',
|
226
|
+
'path' => 'http://example.com/path',
|
227
|
+
}
|
228
|
+
|
229
|
+
@client.store_content 'new_key', 'test', ''
|
230
|
+
|
231
|
+
expected = <<-EOF
|
232
|
+
PUT /path HTTP/1.0\r
|
233
|
+
Content-Length: 0\r
|
234
|
+
\r
|
235
|
+
EOF
|
236
|
+
|
237
|
+
assert_equal expected, socket.write_s.string
|
238
|
+
|
239
|
+
assert_equal ['example.com', 80], TCPSocket.connections.shift
|
240
|
+
assert_empty TCPSocket.connections
|
241
|
+
end
|
242
|
+
|
243
|
+
def test_store_content_nfs
|
244
|
+
@backend.create_open = {
|
245
|
+
'dev_count' => '1',
|
246
|
+
'devid_1' => '1',
|
247
|
+
'path_1' => '/path',
|
248
|
+
}
|
249
|
+
|
250
|
+
@client.store_content 'new_key', 'test', 'data'
|
251
|
+
|
252
|
+
dest_file = File.join(@root, 'path')
|
253
|
+
|
254
|
+
assert File.exist?(dest_file)
|
255
|
+
assert_equal 'data', File.read(dest_file)
|
256
|
+
end
|
257
|
+
|
258
|
+
def test_store_content_nfs_empty
|
259
|
+
@backend.create_open = {
|
260
|
+
'dev_count' => '1',
|
261
|
+
'devid_1' => '1',
|
262
|
+
'path_1' => '/path',
|
263
|
+
}
|
264
|
+
|
265
|
+
@client.store_content 'new_key', 'test', ''
|
266
|
+
|
267
|
+
dest_file = File.join(@root, 'path')
|
268
|
+
|
269
|
+
assert File.exist?(dest_file)
|
270
|
+
assert_equal '', File.read(dest_file)
|
271
|
+
end
|
272
|
+
|
117
273
|
def test_store_content_readonly
|
118
274
|
@client.readonly = true
|
275
|
+
|
119
276
|
assert_raises RuntimeError do
|
120
277
|
@client.store_content 'new_key', 'test', nil
|
121
278
|
end
|
@@ -130,23 +287,34 @@ class TestMogileFS__MogileFS < TestMogileFS
|
|
130
287
|
|
131
288
|
def test_rename_existing
|
132
289
|
@backend.rename = {}
|
133
|
-
|
134
|
-
|
135
|
-
end
|
290
|
+
|
291
|
+
assert_nil @client.rename('from_key', 'to_key')
|
136
292
|
end
|
137
293
|
|
138
294
|
def test_rename_nonexisting
|
139
295
|
@backend.rename = 'unknown_key', ''
|
140
|
-
|
141
|
-
|
296
|
+
|
297
|
+
assert_nil @client.rename('from_key', 'to_key')
|
298
|
+
end
|
299
|
+
|
300
|
+
def test_rename_no_key
|
301
|
+
@backend.rename = 'no_key', ''
|
302
|
+
|
303
|
+
e = assert_raises RuntimeError do
|
304
|
+
@client.rename 'new_key', 'test'
|
142
305
|
end
|
306
|
+
|
307
|
+
assert_equal 'unable to rename new_key to test: no_key', e.message
|
143
308
|
end
|
144
309
|
|
145
310
|
def test_rename_readonly
|
146
311
|
@client.readonly = true
|
147
|
-
|
312
|
+
|
313
|
+
e = assert_raises RuntimeError do
|
148
314
|
@client.rename 'new_key', 'test'
|
149
315
|
end
|
316
|
+
|
317
|
+
assert_equal 'readonly mogilefs', e.message
|
150
318
|
end
|
151
319
|
|
152
320
|
def test_sleep
|
metadata
CHANGED
@@ -1,45 +1,56 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
|
-
rubygems_version: 0.9.4.3
|
3
|
-
specification_version: 1
|
4
2
|
name: mogilefs-client
|
5
3
|
version: !ruby/object:Gem::Version
|
6
|
-
version: 1.
|
7
|
-
date: 2007-07-31 00:00:00 -07:00
|
8
|
-
summary: A Ruby MogileFS client
|
9
|
-
require_paths:
|
10
|
-
- lib
|
11
|
-
email: drbrain@segment7.net
|
12
|
-
homepage: http://seattlerb.org/mogilefs-client
|
13
|
-
rubyforge_project: seattlerb
|
14
|
-
description: A Ruby MogileFS client. MogileFS is a distributed filesystem written by Danga Interactive. This client supports NFS and HTTP modes.
|
15
|
-
autorequire:
|
16
|
-
default_executable:
|
17
|
-
bindir: bin
|
18
|
-
has_rdoc: true
|
19
|
-
required_ruby_version: !ruby/object:Gem::Requirement
|
20
|
-
requirements:
|
21
|
-
- - ">"
|
22
|
-
- !ruby/object:Gem::Version
|
23
|
-
version: 0.0.0
|
24
|
-
version:
|
25
|
-
required_rubygems_version: !ruby/object:Gem::Requirement
|
26
|
-
requirements:
|
27
|
-
- - ">"
|
28
|
-
- !ruby/object:Gem::Version
|
29
|
-
version: 0.0.0
|
30
|
-
version:
|
4
|
+
version: 1.3.0
|
31
5
|
platform: ruby
|
32
|
-
signing_key:
|
33
|
-
cert_chain:
|
34
|
-
post_install_message:
|
35
6
|
authors:
|
7
|
+
- Eric Wong
|
36
8
|
- Eric Hodel
|
9
|
+
autorequire:
|
10
|
+
bindir: bin
|
11
|
+
cert_chain: []
|
12
|
+
|
13
|
+
date: 2008-09-18 00:00:00 -07:00
|
14
|
+
default_executable:
|
15
|
+
dependencies:
|
16
|
+
- !ruby/object:Gem::Dependency
|
17
|
+
name: ZenTest
|
18
|
+
type: :development
|
19
|
+
version_requirement:
|
20
|
+
version_requirements: !ruby/object:Gem::Requirement
|
21
|
+
requirements:
|
22
|
+
- - ">="
|
23
|
+
- !ruby/object:Gem::Version
|
24
|
+
version: 3.6.1
|
25
|
+
version:
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: hoe
|
28
|
+
type: :development
|
29
|
+
version_requirement:
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - ">="
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: 1.7.0
|
35
|
+
version:
|
36
|
+
description: A Ruby MogileFS client. MogileFS is a distributed filesystem written by Danga Interactive. This client supports NFS and HTTP modes.
|
37
|
+
email: normalperson@yhbt.net
|
38
|
+
executables:
|
39
|
+
- mog
|
40
|
+
extensions: []
|
41
|
+
|
42
|
+
extra_rdoc_files:
|
43
|
+
- History.txt
|
44
|
+
- LICENSE.txt
|
45
|
+
- Manifest.txt
|
46
|
+
- README.txt
|
37
47
|
files:
|
38
48
|
- History.txt
|
39
49
|
- LICENSE.txt
|
40
50
|
- Manifest.txt
|
41
51
|
- README.txt
|
42
52
|
- Rakefile
|
53
|
+
- bin/mog
|
43
54
|
- lib/mogilefs.rb
|
44
55
|
- lib/mogilefs/admin.rb
|
45
56
|
- lib/mogilefs/backend.rb
|
@@ -48,39 +59,43 @@ files:
|
|
48
59
|
- lib/mogilefs/mogilefs.rb
|
49
60
|
- lib/mogilefs/nfsfile.rb
|
50
61
|
- lib/mogilefs/pool.rb
|
62
|
+
- lib/mogilefs/util.rb
|
51
63
|
- test/setup.rb
|
52
64
|
- test/test_admin.rb
|
53
65
|
- test/test_backend.rb
|
54
66
|
- test/test_client.rb
|
55
67
|
- test/test_mogilefs.rb
|
56
68
|
- test/test_pool.rb
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
- test/test_client.rb
|
61
|
-
- test/test_mogilefs.rb
|
62
|
-
- test/test_pool.rb
|
69
|
+
has_rdoc: true
|
70
|
+
homepage: http://seattlerb.org/mogilefs-client
|
71
|
+
post_install_message:
|
63
72
|
rdoc_options:
|
64
73
|
- --main
|
65
74
|
- README.txt
|
66
|
-
|
67
|
-
-
|
68
|
-
|
69
|
-
|
70
|
-
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
+
require_paths:
|
76
|
+
- lib
|
77
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
78
|
+
requirements:
|
79
|
+
- - ">="
|
80
|
+
- !ruby/object:Gem::Version
|
81
|
+
version: "0"
|
82
|
+
version:
|
83
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: "0"
|
88
|
+
version:
|
75
89
|
requirements: []
|
76
90
|
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
91
|
+
rubyforge_project: seattlerb
|
92
|
+
rubygems_version: 1.2.0
|
93
|
+
signing_key:
|
94
|
+
specification_version: 2
|
95
|
+
summary: A Ruby MogileFS client
|
96
|
+
test_files:
|
97
|
+
- test/test_pool.rb
|
98
|
+
- test/test_mogilefs.rb
|
99
|
+
- test/test_client.rb
|
100
|
+
- test/test_admin.rb
|
101
|
+
- test/test_backend.rb
|