Remove NFS support
[ruby-mogilefs-client.git] / lib / mogilefs / mogilefs.rb
bloba1fc2636d29a570ea20f4ebd754e9909c7393173
1 require 'mogilefs/client'
2 require 'mogilefs/util'
4 ##
5 # MogileFS File manipulation client.
7 class MogileFS::MogileFS < MogileFS::Client
9   include MogileFS::Util
10   include MogileFS::Bigfile
12   ##
13   # The domain of keys for this MogileFS client.
15   attr_reader :domain
17   ##
18   # The timeout for get_file_data.  Defaults to five seconds.
20   attr_accessor :get_file_data_timeout
22   ##
23   # Creates a new MogileFS::MogileFS instance.  +args+ must include a key
24   # :domain specifying the domain of this client.
26   def initialize(args = {})
27     @domain = args[:domain]
29     @get_file_data_timeout = 5
31     raise ArgumentError, "you must specify a domain" unless @domain
33     if @backend = args[:db_backend]
34       @readonly = true
35     else
36       super
37     end
38   end
40   ##
41   # Enumerates keys starting with +key+.
43   def each_key(prefix)
44     after = nil
46     keys, after = list_keys prefix
48     until keys.nil? or keys.empty? do
49       keys.each { |k| yield k }
50       keys, after = list_keys prefix, after
51     end
53     return nil
54   end
56   ##
57   # Retrieves the contents of +key+.
59   def get_file_data(key, &block)
60     paths = get_paths key
62     return nil unless paths
64     paths.each do |path|
65       next unless path
66       case path
67       when /^http:\/\// then
68         begin
69           sock = http_get_sock(URI.parse(path))
70           return block_given? ? yield(sock) : sock.read
71         rescue MogileFS::Timeout, Errno::ECONNREFUSED,
72                EOFError, SystemCallError
73           next
74         end
75       else
76         next unless File.exist? path
77         return File.read(path)
78       end
79     end
81     return nil
82   end
84   ##
85   # Get the paths for +key+.
87   def get_paths(key, noverify = true, zone = nil)
88     noverify = noverify ? 1 : 0
89     res = @backend.get_paths(:domain => @domain, :key => key,
90                              :noverify => noverify, :zone => zone)
91     paths = (1..res['paths'].to_i).map { |i| res["path#{i}"] }
92     return paths if paths.empty?
93     return paths if paths.first =~ /^http:\/\//
94   end
96   ##
97   # Creates a new file +key+ in +klass+.  +bytes+ is currently unused.
98   #
99   # The +block+ operates like File.open.
101   def new_file(key, klass, bytes = 0, &block) # :yields: file
102     raise MogileFS::ReadOnlyError if readonly?
104     res = @backend.create_open(:domain => @domain, :class => klass,
105                                :key => key, :multi_dest => 1)
107     dests = nil
109     if res.include? 'dev_count' then # HACK HUH?
110       dests = (1..res['dev_count'].to_i).map do |i|
111         [res["devid_#{i}"], res["path_#{i}"]]
112       end
113     else
114       # 0x0040:  d0e4 4f4b 2064 6576 6964 3d31 2666 6964  ..OK.devid=1&fid
115       # 0x0050:  3d33 2670 6174 683d 6874 7470 3a2f 2f31  =3&path=http://1
116       # 0x0060:  3932 2e31 3638 2e31 2e37 323a 3735 3030  92.168.1.72:7500
117       # 0x0070:  2f64 6576 312f 302f 3030 302f 3030 302f  /dev1/0/000/000/
118       # 0x0080:  3030 3030 3030 3030 3033 2e66 6964 0d0a  0000000003.fid..
120       dests = [[res['devid'], res['path']]]
121     end
123     dest = dests.first
124     devid, path = dest
126     case path
127     when nil, '' then
128       raise MogileFS::EmptyPathError
129     when /^http:\/\// then
130       MogileFS::HTTPFile.open(self, res['fid'], path, devid, klass, key,
131                               dests, bytes, &block)
132     else
133       raise MogileFS::UnsupportedPathError,
134             "path '#{path}' returned by backend is not supported"
135     end
136   end
138   ##
139   # Copies the contents of +file+ into +key+ in class +klass+.  +file+ can be
140   # either a file name or an object that responds to #read.
142   def store_file(key, klass, file)
143     raise MogileFS::ReadOnlyError if readonly?
145     new_file key, klass do |mfp|
146       if file.respond_to? :sysread then
147         return sysrwloop(file, mfp)
148       else
149         if File.size(file) > 0x10000 # Bigass file, handle differently
150           mfp.big_io = file
151           return
152         else
153           return File.open(file) { |fp| sysrwloop(fp, mfp) }
154         end
155       end
156     end
157   end
159   ##
160   # Stores +content+ into +key+ in class +klass+.
162   def store_content(key, klass, content)
163     raise MogileFS::ReadOnlyError if readonly?
165     new_file key, klass do |mfp|
166       mfp << content
167     end
169     return content.length
170   end
172   ##
173   # Removes +key+.
175   def delete(key)
176     raise MogileFS::ReadOnlyError if readonly?
178     @backend.delete :domain => @domain, :key => key
179   end
181   ##
182   # Sleeps +duration+.
184   def sleep(duration)
185     @backend.sleep :duration => duration
186   end
188   ##
189   # Renames a key +from+ to key +to+.
191   def rename(from, to)
192     raise MogileFS::ReadOnlyError if readonly?
194     @backend.rename :domain => @domain, :from_key => from, :to_key => to
195     nil
196   end
198   ##
199   # Returns the size of +key+.
200   def size(key)
201     @backend.respond_to?(:_size) and return @backend._size(domain, key)
202     paths = get_paths(key) or return nil
203     paths_size(paths)
204   end
206   def paths_size(paths)
207     paths.each do |path|
208       next unless path
209       case path
210       when /^http:\/\// then
211         begin
212           url = URI.parse path
213           s = Socket.mogilefs_new_request(url.host, url.port,
214                                    "HEAD #{url.request_uri} HTTP/1.0\r\n\r\n",
215                                    @get_file_data_timeout)
216           res = s.recv(4096, 0)
218           if cl = /^Content-Length:\s*(\d+)/i.match(res)
219             return cl[1].to_i
220           end
221           next
222         rescue MogileFS::Timeout, Errno::ECONNREFUSED,
223                EOFError, SystemCallError
224           next
225         end
226       else
227         next unless File.exist? path
228         return File.size(path)
229       end
230     end
232     nil
233   end
235   ##
236   # Lists keys starting with +prefix+ follwing +after+ up to +limit+.  If
237   # +after+ is nil the list starts at the beginning.
239   def list_keys(prefix, after = nil, limit = 1000, &block)
240     if @backend.respond_to?(:_list_keys)
241       return @backend._list_keys(domain, prefix, after, limit, &block)
242     end
244     res = begin
245       @backend.list_keys(:domain => domain, :prefix => prefix,
246                          :after => after, :limit => limit)
247     rescue MogileFS::Backend::NoneMatchError
248       return nil
249     end
251     keys = (1..res['key_count'].to_i).map { |i| res["key_#{i}"] }
252     if block_given?
253       # emulate the MogileFS::Mysql interface, slowly...
254       keys.each do |key|
255         paths = get_paths(key) or next
256         length = paths_size(paths) or next
257         yield key, length, paths.size
258       end
259     end
261     return keys, res['next_after']
262   end
264   protected
266     def http_get_sock(uri)
267       sock = Socket.mogilefs_new_request(uri.host, uri.port,
268                                     "GET #{uri.request_uri} HTTP/1.0\r\n\r\n",
269                                     @get_file_data_timeout)
270       buf = sock.recv(4096, Socket::MSG_PEEK)
271       head, body = buf.split(/\r\n\r\n/, 2)
272       head = sock.recv(head.size + 4, 0)
274       sock
275     end # def http_get_sock