1 # -*- encoding: binary -*-
2 require 'mogilefs/client'
3 require 'mogilefs/util'
4 require 'mogilefs/http_reader'
7 # MogileFS File manipulation client.
9 class MogileFS::MogileFS < MogileFS::Client
11 include MogileFS::Util
12 include MogileFS::Bigfile
15 # The domain of keys for this MogileFS client.
20 # The timeout for get_file_data. Defaults to five seconds.
22 attr_accessor :get_file_data_timeout
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]
43 # Enumerates keys starting with +key+.
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
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
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
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) }
86 # Creates a new file +key+ in +klass+. +bytes+ is currently unused.
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}"]]
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']]]
110 case (dests[0][1] rescue nil)
112 raise MogileFS::EmptyPathError
113 when /^http:\/\// then
114 httpfile = MogileFS::HTTPFile.new(dests, bytes)
117 @backend.create_close(:fid => res['fid'],
118 :devid => httpfile.devid,
121 :path => httpfile.uri.to_s,
125 raise MogileFS::UnsupportedPathError,
126 "paths '#{dests.inspect}' returned by backend is not supported"
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)
142 size = File.size(file)
143 if size > 0x10000 # Bigass file, handle differently
147 File.open(file, "rb") { |fp| copy_stream(fp, mfp) }
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
174 raise MogileFS::ReadOnlyError if readonly?
176 @backend.delete :domain => @domain, :key => key
183 @backend.sleep :duration => duration
187 # Renames a key +from+ to key +to+.
190 raise MogileFS::ReadOnlyError if readonly?
192 @backend.rename :domain => @domain, :from_key => from, :to_key => to
197 # Returns the size of +key+.
199 @backend.respond_to?(:_size) and return @backend._size(domain, key)
200 paths = get_paths(key)
204 def paths_size(paths)
205 sock = MogileFS::HTTPReader.first(paths, "HEAD", @get_file_data_timeout)
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)
219 @backend.list_keys(:domain => domain, :prefix => prefix,
220 :after => after, :limit => limit)
221 rescue MogileFS::Backend::NoneMatchError
225 keys = (1..res['key_count'].to_i).map { |i| res["key_#{i}"] }
227 # emulate the MogileFS::Mysql interface, slowly...
229 paths = get_paths(key)
230 length = paths_size(paths)
231 yield key, length, paths.size
235 [ keys, res['next_after'] ]