4 require 'mogilefs/client'
5 require 'mogilefs/nfsfile'
6 require 'mogilefs/util'
11 class MogileFS::Timeout < Timeout::Error; end
14 # MogileFS File manipulation client.
16 class MogileFS::MogileFS < MogileFS::Client
18 include MogileFS::Util
19 include MogileFS::Bigfile
22 # The path to the local MogileFS mount point if you are using NFS mode.
27 # The domain of keys for this MogileFS client.
32 # The timeout for get_file_data. Defaults to five seconds.
34 attr_accessor :get_file_data_timeout
37 # Creates a new MogileFS::MogileFS instance. +args+ must include a key
38 # :domain specifying the domain of this client. A key :root will be used to
39 # specify the root of the NFS file system.
41 def initialize(args = {})
42 @domain = args[:domain]
45 @get_file_data_timeout = 5
47 raise ArgumentError, "you must specify a domain" unless @domain
49 if @backend = args[:db_backend]
57 # Enumerates keys starting with +key+.
62 keys, after = list_keys prefix
64 until keys.nil? or keys.empty? do
65 keys.each { |k| yield k }
66 keys, after = list_keys prefix, after
73 # Retrieves the contents of +key+.
75 def get_file_data(key, &block)
78 return nil unless paths
83 when /^http:\/\// then
85 sock = http_get_sock(URI.parse(path))
86 return block_given? ? yield(sock) : sock.read
87 rescue MogileFS::Timeout, Errno::ECONNREFUSED,
88 EOFError, SystemCallError
92 next unless File.exist? path
93 return File.read(path)
101 # Get the paths for +key+.
103 def get_paths(key, noverify = true, zone = nil)
104 noverify = noverify ? 1 : 0
105 res = @backend.get_paths(:domain => @domain, :key => key,
106 :noverify => noverify, :zone => zone)
107 paths = (1..res['paths'].to_i).map { |i| res["path#{i}"] }
108 return paths if paths.empty?
109 return paths if paths.first =~ /^http:\/\//
110 return paths.map { |path| File.join @root, path }
114 # Creates a new file +key+ in +klass+. +bytes+ is currently unused.
116 # The +block+ operates like File.open.
118 def new_file(key, klass, bytes = 0, &block) # :yields: file
119 raise MogileFS::ReadOnlyError if readonly?
121 res = @backend.create_open(:domain => @domain, :class => klass,
122 :key => key, :multi_dest => 1)
126 if res.include? 'dev_count' then # HACK HUH?
127 dests = (1..res['dev_count'].to_i).map do |i|
128 [res["devid_#{i}"], res["path_#{i}"]]
131 # 0x0040: d0e4 4f4b 2064 6576 6964 3d31 2666 6964 ..OK.devid=1&fid
132 # 0x0050: 3d33 2670 6174 683d 6874 7470 3a2f 2f31 =3&path=http://1
133 # 0x0060: 3932 2e31 3638 2e31 2e37 323a 3735 3030 92.168.1.72:7500
134 # 0x0070: 2f64 6576 312f 302f 3030 302f 3030 302f /dev1/0/000/000/
135 # 0x0080: 3030 3030 3030 3030 3033 2e66 6964 0d0a 0000000003.fid..
137 dests = [[res['devid'], res['path']]]
146 when /^http:\/\// then
147 MogileFS::HTTPFile.open(self, res['fid'], path, devid, klass, key,
148 dests, bytes, &block)
150 MogileFS::NFSFile.open(self, res['fid'], path, devid, klass, key, &block)
155 # Copies the contents of +file+ into +key+ in class +klass+. +file+ can be
156 # either a file name or an object that responds to #read.
158 def store_file(key, klass, file)
159 raise MogileFS::ReadOnlyError if readonly?
161 new_file key, klass do |mfp|
162 if file.respond_to? :sysread then
163 return sysrwloop(file, mfp)
165 if File.size(file) > 0x10000 # Bigass file, handle differently
169 return File.open(file) { |fp| sysrwloop(fp, mfp) }
176 # Stores +content+ into +key+ in class +klass+.
178 def store_content(key, klass, content)
179 raise MogileFS::ReadOnlyError if readonly?
181 new_file key, klass do |mfp|
185 return content.length
192 raise MogileFS::ReadOnlyError if readonly?
194 @backend.delete :domain => @domain, :key => key
201 @backend.sleep :duration => duration
205 # Renames a key +from+ to key +to+.
208 raise MogileFS::ReadOnlyError if readonly?
210 @backend.rename :domain => @domain, :from_key => from, :to_key => to
215 # Returns the size of +key+.
217 @backend.respond_to?(:_size) and return @backend._size(domain, key)
218 paths = get_paths(key) or return nil
222 def paths_size(paths)
226 when /^http:\/\// then
230 res = timeout @get_file_data_timeout, MogileFS::Timeout do
231 s = TCPSocket.new(url.host, url.port)
232 s.syswrite("HEAD #{url.request_uri} HTTP/1.0\r\n\r\n")
235 if cl = /^Content-Length:\s*(\d+)/i.match(res)
239 rescue MogileFS::Timeout, Errno::ECONNREFUSED,
240 EOFError, SystemCallError
244 next unless File.exist? path
245 return File.size(path)
253 # Lists keys starting with +prefix+ follwing +after+ up to +limit+. If
254 # +after+ is nil the list starts at the beginning.
256 def list_keys(prefix, after = nil, limit = 1000, &block)
257 if @backend.respond_to?(:_list_keys)
258 return @backend._list_keys(domain, prefix, after, limit, &block)
262 @backend.list_keys(:domain => domain, :prefix => prefix,
263 :after => after, :limit => limit)
264 rescue MogileFS::Backend::NoneMatchError
268 keys = (1..res['key_count'].to_i).map { |i| res["key_#{i}"] }
270 # emulate the MogileFS::Mysql interface, slowly...
272 paths = get_paths(key) or next
273 length = paths_size(paths) or next
274 yield key, length, paths.size
278 return keys, res['next_after']
283 def http_get_sock(uri)
285 timeout @get_file_data_timeout, MogileFS::Timeout do
286 sock = TCPSocket.new(uri.host, uri.port)
288 sock.syswrite("GET #{uri.request_uri} HTTP/1.0\r\n\r\n")
289 buf = sock.recv(4096, Socket::MSG_PEEK)
290 head, body = buf.split(/\r\n\r\n/, 2)
291 head = sock.recv(head.size + 4)
295 end # def http_get_sock