refactor httpfile and remove layering violation
[ruby-mogilefs-client.git] / lib / mogilefs / mogilefs.rb
blob267c8968380c55ac2c4a8cb4d10c9cc7c8fc7a76
1 # -*- encoding: binary -*-
2 require 'mogilefs/client'
3 require 'mogilefs/util'
4 require 'mogilefs/http_reader'
6 ##
7 # MogileFS File manipulation client.
9 class MogileFS::MogileFS < MogileFS::Client
11   include MogileFS::Util
12   include MogileFS::Bigfile
14   ##
15   # The domain of keys for this MogileFS client.
17   attr_reader :domain
19   ##
20   # The timeout for get_file_data.  Defaults to five seconds.
22   attr_accessor :get_file_data_timeout
24   ##
25   # Creates a new MogileFS::MogileFS instance.  +args+ must include a key
26   # :domain specifying the domain of this client.
28   def initialize(args = {})
29     @domain = args[:domain]
31     @get_file_data_timeout = 5
33     raise ArgumentError, "you must specify a domain" unless @domain
35     if @backend = args[:db_backend]
36       @readonly = true
37     else
38       super
39     end
40   end
42   ##
43   # Enumerates keys starting with +key+.
45   def each_key(prefix)
46     after = nil
48     keys, after = list_keys prefix
50     until keys.nil? or keys.empty? do
51       keys.each { |k| yield k }
52       keys, after = list_keys prefix, after
53     end
55     nil
56   end
58   ##
59   # Retrieves the contents of +key+.
61   def get_file_data(key)
62     paths = get_paths(key)
63     sock = MogileFS::HTTPReader.first(paths, "GET", @get_file_data_timeout)
64     block_given? ? yield(sock) : sock.to_s
65   end
67   ##
68   # Get the paths for +key+.
70   def get_paths(key, noverify = true, zone = nil)
71     opts = { :domain => @domain, :key => key,
72              :noverify => noverify ? 1 : 0, :zone => zone }
73     @backend.respond_to?(:_get_paths) and return @backend._get_paths(opts)
74     res = @backend.get_paths(opts)
75     (1..res['paths'].to_i).map { |i| res["path#{i}"] }.compact
76   end
78   ##
79   # Get the URIs for +key+.
81   def get_uris(key, noverify = true, zone = nil)
82     get_paths(key, noverify, zone).map { |path| URI.parse(path) }
83   end
85   ##
86   # Creates a new file +key+ in +klass+.  +bytes+ is currently unused.
87   #
88   # The +block+ operates like File.open.
90   def new_file(key, klass = nil, bytes = 0) # :yields: file
91     raise MogileFS::ReadOnlyError if readonly?
92     opts = { :domain => @domain, :key => key, :multi_dest => 1 }
93     opts[:class] = klass if klass && klass != "default"
94     res = @backend.create_open(opts)
96     dests = if dev_count = res['dev_count'] # multi_dest succeeded
97       (1..dev_count.to_i).map do |i|
98         [res["devid_#{i}"], res["path_#{i}"]]
99       end
100     else # single destination returned
101       # 0x0040:  d0e4 4f4b 2064 6576 6964 3d31 2666 6964  ..OK.devid=1&fid
102       # 0x0050:  3d33 2670 6174 683d 6874 7470 3a2f 2f31  =3&path=http://1
103       # 0x0060:  3932 2e31 3638 2e31 2e37 323a 3735 3030  92.168.1.72:7500
104       # 0x0070:  2f64 6576 312f 302f 3030 302f 3030 302f  /dev1/0/000/000/
105       # 0x0080:  3030 3030 3030 3030 3033 2e66 6964 0d0a  0000000003.fid..
107       [[res['devid'], res['path']]]
108     end
110     case (dests[0][1] rescue nil)
111     when nil, '' then
112       raise MogileFS::EmptyPathError
113     when /^http:\/\// then
114       httpfile = MogileFS::HTTPFile.new(dests, bytes)
115       yield httpfile
116       rv = httpfile.commit
117       @backend.create_close(:fid => res['fid'],
118                             :devid => httpfile.devid,
119                             :domain => @domain,
120                             :key => key,
121                             :path => httpfile.uri.to_s,
122                             :size => rv)
123       rv
124     else
125       raise MogileFS::UnsupportedPathError,
126             "paths '#{dests.inspect}' returned by backend is not supported"
127     end
128   end
130   ##
131   # Copies the contents of +file+ into +key+ in class +klass+.  +file+ can be
132   # either a file name or an object that responds to #sysread.
133   # Returns size of +file+ stored
135   def store_file(key, klass, file)
136     raise MogileFS::ReadOnlyError if readonly?
138     new_file key, klass do |mfp|
139       if file.respond_to?(:read)
140         copy_stream(file, mfp)
141       else
142         size = File.size(file)
143         if size > 0x10000 # Bigass file, handle differently
144           mfp.big_io = file
145           size
146         else
147           File.open(file, "rb") { |fp| copy_stream(fp, mfp) }
148         end
149       end
150     end
151   end
153   ##
154   # Stores +content+ into +key+ in class +klass+.
156   def store_content(key, klass, content)
157     raise MogileFS::ReadOnlyError if readonly?
159     new_file key, klass do |mfp|
160       if content.is_a?(MogileFS::Util::StoreContent)
161         mfp.streaming_io = content
162       else
163         mfp << content
164       end
165     end
167     content.length
168   end
170   ##
171   # Removes +key+.
173   def delete(key)
174     raise MogileFS::ReadOnlyError if readonly?
176     @backend.delete :domain => @domain, :key => key
177   end
179   ##
180   # Sleeps +duration+.
182   def sleep(duration)
183     @backend.sleep :duration => duration
184   end
186   ##
187   # Renames a key +from+ to key +to+.
189   def rename(from, to)
190     raise MogileFS::ReadOnlyError if readonly?
192     @backend.rename :domain => @domain, :from_key => from, :to_key => to
193     nil
194   end
196   ##
197   # Returns the size of +key+.
198   def size(key)
199     @backend.respond_to?(:_size) and return @backend._size(domain, key)
200     paths = get_paths(key)
201     paths_size(paths)
202   end
204   def paths_size(paths)
205     sock = MogileFS::HTTPReader.first(paths, "HEAD", @get_file_data_timeout)
206     sock.content_length
207   end
209   ##
210   # Lists keys starting with +prefix+ follwing +after+ up to +limit+.  If
211   # +after+ is nil the list starts at the beginning.
213   def list_keys(prefix, after = nil, limit = 1000, &block)
214     if @backend.respond_to?(:_list_keys)
215       return @backend._list_keys(domain, prefix, after, limit, &block)
216     end
218     res = begin
219       @backend.list_keys(:domain => domain, :prefix => prefix,
220                          :after => after, :limit => limit)
221     rescue MogileFS::Backend::NoneMatchError
222       return nil
223     end
225     keys = (1..res['key_count'].to_i).map { |i| res["key_#{i}"] }
226     if block_given?
227       # emulate the MogileFS::Mysql interface, slowly...
228       keys.each do |key|
229         paths = get_paths(key)
230         length = paths_size(paths)
231         yield key, length, paths.size
232       end
233     end
235     [ keys, res['next_after'] ]
236   end