Project

General

Profile

Download (9.84 KB) Statistics
| Branch: | Tag: | Revision:
d1e614b5 Marc Dequènes (Duck)
#--
# CyborgHood, a distributed system management software.
364e4a96 Marc Dequènes (Duck)
# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
d1e614b5 Marc Dequènes (Duck)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#++

require 'singleton'
7466fc08 Marc Dequènes (Duck)
require 'ostruct'
d1e614b5 Marc Dequènes (Duck)

module CyborgHood
7466fc08 Marc Dequènes (Duck)
module DSL
class ServerApiNode < BaseDSL
4761cd13 Marc Dequènes (Duck)
attr_reader :bot, :node_name, :parent_node, :store
8fcaacd6 Marc Dequènes (Duck)
66cff75f Marc Dequènes (Duck)
include TaskAspect

eab0c364 Marc Dequènes (Duck)
def initialize(bot, parent_node = nil, options = {}, &block)
7466fc08 Marc Dequènes (Duck)
@bot = bot
@parent_node = parent_node
eab0c364 Marc Dequènes (Duck)
@ldir = options[:dir]
@hidden = options[:hidden] || false
4761cd13 Marc Dequènes (Duck)
7466fc08 Marc Dequènes (Duck)
# don't call super because we need defered loading
4761cd13 Marc Dequènes (Duck)
@blocks = []
add_behavior(&block)

@ldir_loaded = false
a3483167 Marc Dequènes (Duck)
7466fc08 Marc Dequènes (Duck)
cleanup
a3483167 Marc Dequènes (Duck)
end
8fcaacd6 Marc Dequènes (Duck)
ec7296ea Marc Dequènes (Duck)
def root?
@parent_node.nil?
end

eab0c364 Marc Dequènes (Duck)
def hidden?
@hidden
end

ec7296ea Marc Dequènes (Duck)
def node_path
return '/' if root?

path = @parent_node.node_path
path += "/" + @node_name unless @node_name.empty?
path.gsub("//", "/")
end

7466fc08 Marc Dequènes (Duck)
def add_behavior(&block)
4761cd13 Marc Dequènes (Duck)
if block_given?
@blocks << block
else
return if @ldir_loaded

ec7296ea Marc Dequènes (Duck)
begin
Dir.glob(File.join(self.lpath, '*.rb')) do |file|
logger.debug "Interface: loading file '#{file}'"
begin
@blocks << File.read(file)
rescue
logger.error "Interface: definition file '#{file}' cannot be read"
end
end
rescue
logger.error "Interface: directory '#{self.lpath}' cannot be read"
4761cd13 Marc Dequènes (Duck)
end

@ldir_loaded = true
end
8fcaacd6 Marc Dequènes (Duck)
end

7466fc08 Marc Dequènes (Duck)
# string, array (useful for aliases), or regex
# TODO: name validation
eab0c364 Marc Dequènes (Duck)
def node(match, options = {}, &block)
child_node = self.class.new(@bot, self, options, &block)
7466fc08 Marc Dequènes (Duck)
if match.is_a? Array
match.each{|n| @nodes[n] = child_node}
else
@nodes[match] = child_node
end
8fcaacd6 Marc Dequènes (Duck)
end
69a12fdb Marc Dequènes (Duck)
7bfcd98b Marc Dequènes (Duck)
def attr_search_node(list = nil, &block)
list ||= block

eab0c364 Marc Dequènes (Duck)
lookup_node = self

node '?', :hidden => true do
on_request do |request|
if request.args.empty?
258a439a Marc Dequènes (Duck)
request.reply.results = lookup_node.__send__(:visible_nodes_names, request)
eab0c364 Marc Dequènes (Duck)
else
7bfcd98b Marc Dequènes (Duck)
request.reply.results = list.nil? ? search_among_children(request, lookup_node) :
search_in_list(request, lookup_node, list)
eab0c364 Marc Dequènes (Duck)
end
feb16bd0 Marc Dequènes (Duck)
request.send_reply
eab0c364 Marc Dequènes (Duck)
end
end
end

7466fc08 Marc Dequènes (Duck)
def on_request(&cb)
@request_cb = cb
69a12fdb Marc Dequènes (Duck)
end
d1e614b5 Marc Dequènes (Duck)
258a439a Marc Dequènes (Duck)
def _is_node?(session, env, node_path)
node = __send__(:find_node, session, env, node_path)
7466fc08 Marc Dequènes (Duck)
not node.nil?
end
69a12fdb Marc Dequènes (Duck)
258a439a Marc Dequènes (Duck)
def _call(session, env, node_path, args = nil, &send_result_cb)
7466fc08 Marc Dequènes (Duck)
args ||= []
7bfcd98b Marc Dequènes (Duck)
raise CyberError.new(:unrecoverable, 'api/cyborghood', "wrong format for arguments when calling node '#{node_path}'") unless args.is_a? Array
d1e614b5 Marc Dequènes (Duck)
258a439a Marc Dequènes (Duck)
node = find_node(session, env, node_path)
7bfcd98b Marc Dequènes (Duck)
raise CyberError.new(:unrecoverable, 'api/cyborghood', "unknown node '#{node_path}'") if node.nil?
7466fc08 Marc Dequènes (Duck)
logger.debug "[Server API] Node '#{node_path}' found"
258a439a Marc Dequènes (Duck)
# TODO: validate environment and setup things (choosen locale for example)

node.__send__(:request, session, env, args, &send_result_cb)
7466fc08 Marc Dequènes (Duck)
end
23b2305f Marc Dequènes (Duck)
7466fc08 Marc Dequènes (Duck)
protected
23b2305f Marc Dequènes (Duck)
4761cd13 Marc Dequènes (Duck)
def base_lpath
File.join(Config::LIB_DIR, 'cyborghood-' + @bot.name.downcase, 'interface')
end

def lpath
parent_lpath = @parent_node.nil? ? self.base_lpath : @parent_node.lpath
@ldir.nil? ? parent_lpath : File.join(parent_lpath, @ldir)
end

7466fc08 Marc Dequènes (Duck)
def load(node_element = '')
cleanup
@node_name = node_element
@blocks.each do |bl|
ec7296ea Marc Dequènes (Duck)
begin
if bl.is_a? String
instance_eval bl
else
instance_eval &bl
end
rescue Exception => e
logger.error "Interface: a definition block for node '#{node_path}' is buggy: " + e.message
4761cd13 Marc Dequènes (Duck)
end
7466fc08 Marc Dequènes (Duck)
end
end
1c89625f Marc Dequènes (Duck)
258a439a Marc Dequènes (Duck)
def find_node(session, env, node_path)
4761cd13 Marc Dequènes (Duck)
# node_path is a string argument when interface root node is called, but is a list of node elements later on
7466fc08 Marc Dequènes (Duck)
if root?
logger.debug "[Server API] Looking for node '#{node_path}'"
node_path = node_path.split("/")
# remove empty string before first "/"
node_path.shift
# initial load
load
end
41802ec1 Marc Dequènes (Duck)
7466fc08 Marc Dequènes (Duck)
node_element = node_path.shift
logger.debug "[Server API] Looking for node element '#{node_element}'"
if node_element.nil?
return self
41802ec1 Marc Dequènes (Duck)
else
258a439a Marc Dequènes (Duck)
next_node = find_child_node(session, env, node_element)
7466fc08 Marc Dequènes (Duck)
if next_node
next_node.__send__(:load, node_element)
258a439a Marc Dequènes (Duck)
return next_node.__send__(:find_node, session, env, node_path)
7466fc08 Marc Dequènes (Duck)
else
return
end
41802ec1 Marc Dequènes (Duck)
end
end

258a439a Marc Dequènes (Duck)
def find_child_node(session, env, child_node)
7466fc08 Marc Dequènes (Duck)
return @nodes[child_node] if @nodes.has_key? child_node

@nodes.each_pair do |match, node|
found = if match.is_a? String
child_node == match
elsif match.is_a? Regexp
child_node =~ Regexp.new(match)
elsif match.is_a? Proc
258a439a Marc Dequènes (Duck)
match.call(session, env).include? child_node
7466fc08 Marc Dequènes (Duck)
end
return node if found
end
f94d64b1 Marc Dequènes (Duck)
7466fc08 Marc Dequènes (Duck)
nil
d1e614b5 Marc Dequènes (Duck)
end

7466fc08 Marc Dequènes (Duck)
class Request
258a439a Marc Dequènes (Duck)
attr_reader :session, :env, :args, :reply
b7f7d214 Marc Dequènes (Duck)
258a439a Marc Dequènes (Duck)
def initialize(session, env, args = [], &send_result_cb)
7466fc08 Marc Dequènes (Duck)
@session = session
258a439a Marc Dequènes (Duck)
@env = env.to_ostruct
7466fc08 Marc Dequènes (Duck)
@args = args
feb16bd0 Marc Dequènes (Duck)
@send_result_cb = send_result_cb
b7f7d214 Marc Dequènes (Duck)
7466fc08 Marc Dequènes (Duck)
@reply = {
:results => {},
:infos => [],
:warnings => [],
:errors => []
}.to_ostruct
b7f7d214 Marc Dequènes (Duck)
end
feb16bd0 Marc Dequènes (Duck)
def send_reply
@send_result_cb.call @reply.to_hash
end
b7f7d214 Marc Dequènes (Duck)
end

258a439a Marc Dequènes (Duck)
def request(session, env, args = [], &send_result_cb)
request = Request.new(session, env, args, &send_result_cb)
feb16bd0 Marc Dequènes (Duck)
7466fc08 Marc Dequènes (Duck)
if @request_cb
begin
@request_cb.call(request)
rescue
logger.debug "node request error message: " + $!
logger.debug "node request error backtrace: " + $!.backtrace.join("\n")
7bfcd98b Marc Dequènes (Duck)
raise CyberError.new(:unrecoverable, 'api/cyborghood', "call failed on node '#{node_path}': " + $!)
b7f7d214 Marc Dequènes (Duck)
end
7466fc08 Marc Dequènes (Duck)
else
258a439a Marc Dequènes (Duck)
request.reply.results = visible_nodes_names(request)
feb16bd0 Marc Dequènes (Duck)
request.send_reply
eab0c364 Marc Dequènes (Duck)
end
end

def visible_nodes
Hash[@nodes.select{|match, node| not node.hidden? }]
end

258a439a Marc Dequènes (Duck)
def node_match_to_name(request, match)
eab0c364 Marc Dequènes (Duck)
if match.is_a? String
match
elsif match.is_a? Regexp
'/' + match.to_s + '/'
elsif match.is_a? Proc
258a439a Marc Dequènes (Duck)
match.call(request.session, request.env)
b7f7d214 Marc Dequènes (Duck)
end
end
d1e614b5 Marc Dequènes (Duck)
258a439a Marc Dequènes (Duck)
def visible_nodes_names(request)
eab0c364 Marc Dequènes (Duck)
visible_nodes.keys.collect do |match|
258a439a Marc Dequènes (Duck)
node_match_to_name(request, match)
eab0c364 Marc Dequènes (Duck)
end.compact.flatten
end

7466fc08 Marc Dequènes (Duck)
def cleanup
@nodes = {}
@request_cb = nil
4761cd13 Marc Dequènes (Duck)
# data memorized during walk in the node tree
if root?
@store = OpenStruct.new
else
@store = @parent_node.store
end
a3483167 Marc Dequènes (Duck)
end
eab0c364 Marc Dequènes (Duck)
def hash_match_criterias(hash, crit)
crit.each do |key, wanted_value|
2c35cfa1 Marc Dequènes (Duck)
value = hash[key]
eab0c364 Marc Dequènes (Duck)
if wanted_value.is_a? Array
return false unless wanted_value.include?(value)
elsif wanted_value.is_a? Regexp
return false unless value =~ wanted_value
else
return false unless value == wanted_value
end
end

true
end
7bfcd98b Marc Dequènes (Duck)
def search_among_children(request, lookup_node)
ee2c32c3 Marc Dequènes (Duck)
criterias = request.args.first
return {} unless criterias.is_a? Hash

node_names_list = {}
7bfcd98b Marc Dequènes (Duck)
lookup_node.__send__(:visible_nodes).each do |match, node|
if match.is_a? String
match_list = [match]
elsif match.is_a? Proc
258a439a Marc Dequènes (Duck)
match_list = match.call(request.session, request.env)
7bfcd98b Marc Dequènes (Duck)
else
next
end

258a439a Marc Dequènes (Duck)
# TODO: filter by auth token
7bfcd98b Marc Dequènes (Duck)
match_list.each do |child_node_name|
node.__send__(:load, child_node_name)
258a439a Marc Dequènes (Duck)
result = node.__send__(:request, request.session, request.env)
7bfcd98b Marc Dequènes (Duck)
next unless result.respond_to? :to_hash

ee2c32c3 Marc Dequènes (Duck)
child_node_attrs = result.to_hash

if hash_match_criterias(child_node_attrs, criterias)
node_names_list[child_node_name] = child_node_attrs
7bfcd98b Marc Dequènes (Duck)
end
end
end

node_names_list
end

def search_in_list(request, lookup_node, list)
criterias = request.args.first
ee2c32c3 Marc Dequènes (Duck)
return {} unless criterias.is_a? Hash
7bfcd98b Marc Dequènes (Duck)
if list.is_a? Proc
258a439a Marc Dequènes (Duck)
data = list.call(request.session, request.env, criterias)
ee2c32c3 Marc Dequènes (Duck)
return data[:list] unless data[:post_filter]
7bfcd98b Marc Dequènes (Duck)
end

ee2c32c3 Marc Dequènes (Duck)
node_names_list = {}

data[:list].each do |element_name, element|
element_attrs = element.to_hash
7bfcd98b Marc Dequènes (Duck)
ee2c32c3 Marc Dequènes (Duck)
if hash_match_criterias(element_attrs, criterias)
node_names_list[element_name] = element_attrs
7bfcd98b Marc Dequènes (Duck)
end
end

node_names_list
end
a3483167 Marc Dequènes (Duck)
end
7bfcd98b Marc Dequènes (Duck)
end # DSL
d1e614b5 Marc Dequènes (Duck)
end