# -*- encoding: binary -*- require "socket" require "set" module Regurgitator::Local # This is used to register local storage endpoints so we can # short-circuit and avoid making HTTP requests to devices # Must be configured by the user STORE_PATHS = Hash.new do |h,port| h[port] = {} # key: directory root path, value: whatever... end.compare_by_identity @local_addrs = {} @local_addrs_lock = Mutex.new def self.include?(addr) @local_addrs_lock.synchronize { @local_addrs.include?(addr) } end def self.addr (@local_addrs_lock.synchronize { @local_addrs.first })[0] end def self.addrs @local_addrs_lock.synchronize { @local_addrs.keys } end # Normally not needed unless you dynamically bring up/down network devices. # It may be useful to call this periodically or to integrate with # some network device state monitoring system. def self.refresh_addrs! @local_addrs_lock.synchronize do tmp = {} Socket.ip_address_list.keep_if do |ip| ip.ipv4? && ! ip.ipv4_multicast? end.each { |ip| tmp[ip.ip_address.freeze] = true } @local_addrs = tmp end end refresh_addrs! # registers a local path for a given +tcp_port+ and +directory_root+ def self.register(tcp_port, directory_root) directory_root = directory_root.gsub(%r{/+\z}, "") dev_dirs = Dir.foreach(directory_root).grep(/\Adev\d+\z/) raise ArgumentError, 'no /dev\d+/ directories found' if dev_dirs.empty? STORE_PATHS[tcp_port][directory_root] = Set.new(dev_dirs) end # returns +nil+ if nothing was found # returns a path and associated File::Stat to the local FS if a # matching path is possible def device_path_stat(uri) Regurgitator::Local.include?(uri.host) or return (roots = STORE_PATHS[uri.port]).empty? and return uri.path =~ %r{\A/(dev\d+)/} or abort "BUG: path needs to match '\\A/dev\d+/' (#{uri})" devN = $1 rv = nil roots.each do |root, dev_dirs| dev_dirs.include?(devN) or next begin path = "#{root}#{uri.path}" stat = File.stat(path) # multiple "/devN" paths for the same ports would be ambiguous, # so we cannot optimize away the HTTP request for those return nil if rv rv = [ path, stat ] rescue => e warn "E: #{e.message}, root=#{root} failed for #{uri}" end end rv end def trylocal(env, uri_group) STORE_PATHS.empty? and return uri_group.flatten.each do |uri| path_stat = device_path_stat(uri) or next path, stat = path_stat return Regurgitator::LocalFile.new(env, path, uri, stat) end nil end end