flesh out pipe streaming + chunk for uploads
[ruby-mogilefs-client.git] / lib / mogilefs / mogilefs.rb
blobe62e19bb1e698d3a6596d1d293aa2b19c1fbfe3e
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::Bigfile
13   ##
14   # The domain of keys for this MogileFS client.
16   attr_reader :domain
18   ##
19   # The timeout for get_file_data.  Defaults to five seconds.
21   attr_accessor :get_file_data_timeout
23   ##
24   # Creates a new MogileFS::MogileFS instance.  +args+ must include a key
25   # :domain specifying the domain of this client.
27   def initialize(args = {})
28     @domain = args[:domain]
30     @get_file_data_timeout = 5
32     raise ArgumentError, "you must specify a domain" unless @domain
34     if @backend = args[:db_backend]
35       @readonly = true
36     else
37       super
38     end
39   end
41   ##
42   # Enumerates keys starting with +key+.
44   def each_key(prefix)
45     after = nil
47     keys, after = list_keys prefix
49     until keys.nil? or keys.empty? do
50       keys.each { |k| yield k }
51       keys, after = list_keys prefix, after
52     end
54     nil
55   end
57   ##
58   # Retrieves the contents of +key+.
60   def get_file_data(key)
61     paths = get_paths(key)
62     sock = MogileFS::HTTPReader.first(paths, "GET", @get_file_data_timeout)
63     block_given? ? yield(sock) : sock.to_s
64   end
66   ##
67   # Get the paths for +key+.
69   def get_paths(key, noverify = true, zone = nil)
70     opts = { :domain => @domain, :key => key,
71              :noverify => noverify ? 1 : 0, :zone => zone }
72     @backend.respond_to?(:_get_paths) and return @backend._get_paths(opts)
73     res = @backend.get_paths(opts)
74     (1..res['paths'].to_i).map { |i| res["path#{i}"] }.compact
75   end
77   ##
78   # Get the URIs for +key+.
80   def get_uris(key, noverify = true, zone = nil)
81     get_paths(key, noverify, zone).map { |path| URI.parse(path) }
82   end
84   ##
85   # Creates a new file +key+ in +klass+.  +bytes+ is currently unused.
86   #
87   # The +block+ operates like File.open.
89   def new_file(key, klass = nil, bytes = 0) # :yields: file
90     raise MogileFS::ReadOnlyError if readonly?
91     opts = { :domain => @domain, :key => key, :multi_dest => 1 }
92     opts[:class] = klass if klass && klass != "default"
93     res = @backend.create_open(opts)
95     dests = if dev_count = res['dev_count'] # multi_dest succeeded
96       (1..dev_count.to_i).map do |i|
97         [res["devid_#{i}"], res["path_#{i}"]]
98       end
99     else # single destination returned
100       # 0x0040:  d0e4 4f4b 2064 6576 6964 3d31 2666 6964  ..OK.devid=1&fid
101       # 0x0050:  3d33 2670 6174 683d 6874 7470 3a2f 2f31  =3&path=http://1
102       # 0x0060:  3932 2e31 3638 2e31 2e37 323a 3735 3030  92.168.1.72:7500
103       # 0x0070:  2f64 6576 312f 302f 3030 302f 3030 302f  /dev1/0/000/000/
104       # 0x0080:  3030 3030 3030 3030 3033 2e66 6964 0d0a  0000000003.fid..
106       [[res['devid'], res['path']]]
107     end
109     case (dests[0][1] rescue nil)
110     when /^http:\/\// then
111       http_file = MogileFS::HTTPFile.new(dests, bytes)
112       yield http_file
113       rv = http_file.commit
114       @backend.create_close(:fid => res['fid'],
115                             :devid => http_file.devid,
116                             :domain => @domain,
117                             :key => key,
118                             :path => http_file.uri.to_s,
119                             :size => rv)
120       rv
121     when nil, '' then
122       raise MogileFS::EmptyPathError,
123             "Empty path for mogile upload res=#{res.inspect}"
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 path name (String or Pathname object) or an object that
133   # responds to #readpartial.  Returns size of +file+ stored
135   def store_file(key, klass, file)
136     raise MogileFS::ReadOnlyError if readonly?
138     new_file(key, klass) { |mfp| mfp.big_io = file }
139   end
141   ##
142   # Stores +content+ into +key+ in class +klass+.
144   def store_content(key, klass, content)
145     raise MogileFS::ReadOnlyError if readonly?
147     new_file key, klass do |mfp|
148       if content.is_a?(MogileFS::Util::StoreContent)
149         mfp.streaming_io = content
150       else
151         mfp << content
152       end
153     end
155     content.length
156   end
158   ##
159   # Removes +key+.
161   def delete(key)
162     raise MogileFS::ReadOnlyError if readonly?
164     @backend.delete :domain => @domain, :key => key
165     true
166   end
168   ##
169   # Sleeps +duration+.
171   def sleep(duration)
172     @backend.sleep :duration => duration
173   end
175   ##
176   # Renames a key +from+ to key +to+.
178   def rename(from, to)
179     raise MogileFS::ReadOnlyError if readonly?
181     @backend.rename :domain => @domain, :from_key => from, :to_key => to
182     nil
183   end
185   ##
186   # Returns the size of +key+.
187   def size(key)
188     @backend.respond_to?(:_size) and return @backend._size(domain, key)
189     paths = get_paths(key)
190     paths_size(paths)
191   end
193   def paths_size(paths)
194     sock = MogileFS::HTTPReader.first(paths, "HEAD", @get_file_data_timeout)
195     sock.content_length
196   end
198   ##
199   # Lists keys starting with +prefix+ follwing +after+ up to +limit+.  If
200   # +after+ is nil the list starts at the beginning.
202   def list_keys(prefix, after = nil, limit = 1000, &block)
203     if @backend.respond_to?(:_list_keys)
204       return @backend._list_keys(domain, prefix, after, limit, &block)
205     end
207     res = begin
208       @backend.list_keys(:domain => domain, :prefix => prefix,
209                          :after => after, :limit => limit)
210     rescue MogileFS::Backend::NoneMatchError
211       return nil
212     end
214     keys = (1..res['key_count'].to_i).map { |i| res["key_#{i}"] }
215     if block_given?
216       # emulate the MogileFS::Mysql interface, slowly...
217       keys.each do |key|
218         paths = get_paths(key)
219         length = paths_size(paths)
220         yield key, length, paths.size
221       end
222     end
224     [ keys, res['next_after'] ]
225   end