commit 69a12fdbdbd27a5673fa8b848b4a03c2aa671004
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Fri Nov 5 00:23:42 2010 +0100

    [evol] conversation/bot protocol rework §6 (refs #30)

diff --git a/lib/cyborghood/cyborg/conversation.rb b/lib/cyborghood/cyborg/conversation.rb
index 6f951a6..d75be37 100644
--- a/lib/cyborghood/cyborg/conversation.rb
+++ b/lib/cyborghood/cyborg/conversation.rb
@@ -26,6 +26,9 @@ module CyborgHood
 
   module BotProtocol
     VERSION = "0.1"
+    CAPABILITIES = []
+
+    @@request_callback = proc{|result| process_request_result(result) }
 
     # TODO:
     #   - check for request/reply couples (reply to wrong of non-existent request)
@@ -52,6 +55,14 @@ module CyborgHood
       end
     end
 
+    def process_request_result(result)
+      if result[:error]
+        send_error_action(result[:reply_message], result[:error])
+      else
+        send_reply_result(result[:reply_message], result[:action_result])
+      end
+    end
+
     def receive_announce_helo(message)
       unless message.conv_thread.id == 0
         return send_quit_decline "bad negociation"
@@ -81,6 +92,47 @@ module CyborgHood
       @negociation_ok = true
     end
 
+    def receive_request_capabilities(message)
+      send_reply_result(message, @conversation.capabilities + CAPABILITIES)
+    end
+
+    def receive_request_call(message)
+      unless @conversation.bot.interface.is_node? message.parameters[:node]
+        return send_error_action(message, "bad node")
+      end
+      send_reply_ack(message)
+      @conversation.bot.schedule_task(@@request_callback) do
+        result = {
+          :reply_message => message
+        }
+        begin
+          result[:action_result] = @conversation.bot.interface.call(message.conv_thread.session,
+                                                                    message.parameters[:node],
+                                                                    message.parameters[:data])
+        rescue
+          result[:error] = $!
+        end
+      end
+    end
+
+    def receive_request_exists(message)
+      unless @conversation.bot.interface.is_node? message.parameters[:node]
+        return send_error_action(message, "bad node")
+      end
+      send_reply_ack(message)
+      @conversation.bot.schedule_task(@@request_callback) do
+        {
+          :reply_message => message,
+          :action_result => @conversation.bot.interface.has_node? message.parameters[:node]
+        }
+      end
+    end
+
+    def receive_request_describe(message)
+      # TODO: implement when ready in the interface
+      send_quit_decline(message, "not implemented")
+    end
+
     def send_announce_helo(recv_message = nil)
       action_code = "ANNOUNCE HELO"
       message = (recv_message.nil? ? @conversation.thread('system').new_message(action_code) :
@@ -92,13 +144,29 @@ module CyborgHood
       recv_message.create_reply("ANNOUNCE OK").send
     end
 
+    def send_request_capabilities
+      @conversation.thread('system').new_message("REQUEST EXISTS", { :node => node }).send
+    end
+
+    def send_request_call(conv_thread, node)
+      conv_thread.new_message("REQUEST CALL", { :node => node }).send
+    end
+
+    def send_request_exists(conv_thread, node)
+      conv_thread.new_message("REQUEST EXISTS", { :node => node }).send
+    end
+
+    def send_request_describe(conv_thread, node)
+      conv_thread.new_message("REQUEST DESCRIBE", { :node => node }).send
+    end
+
     def send_error_protocol(error, fatal = false)
       @conversation.thread('system').new_message("ERROR PROTOCOL", { :error => error }).send
       @conversation.set_error_status(fatal)
     end
 
-    def send_error_action(recv_message, message)
-      recv_message.create_reply("ERROR ACTION", { :error => message }).send
+    def send_error_action(recv_message, error)
+      recv_message.create_reply("ERROR ACTION", { :error => error }).send
     end
 
     def send_reply_ack(recv_message)
@@ -218,14 +286,8 @@ module CyborgHood
       @protocol = BotProtocol.new(self)
     end
 
-    def clear_receive_info
-      @receive_error = false
-      @receive_fatal_error = false
-    end
-
-    def send_line(msg)
-      send_data "#{msg}\n"
-      logger.debug "Sent data [#{identifier}]: #{msg}"
+    def capabilities
+      []
     end
 
     def post_init
@@ -277,22 +339,6 @@ module CyborgHood
       check_errors
     end
 
-    def check_errors
-      @error_count += 1 if @receive_error
-
-      msg_quit = nil
-      if @error_count >= MAXIMUM_ERROR_COUNT
-        msg_quit = "too much errors, terminating"
-      elsif @fatal_error
-        msg_quit = "previous fatal error"
-      end
-
-      unless msg_quit.nil?
-        send_quit_decline msg_quit
-        close_connection_after_writing
-      end
-    end
-
     def receive_message(message)
       logger.debug "Received message '#{action}' [#{identifier}]"
 
@@ -349,6 +395,32 @@ module CyborgHood
 
     protected
 
+    def clear_receive_info
+      @receive_error = false
+      @receive_fatal_error = false
+    end
+
+    def send_line(msg)
+      logger.debug "Sending data [#{identifier}]: #{msg}"
+      send_data "#{msg}\n"
+    end
+
+    def check_errors
+      @error_count += 1 if @receive_error
+
+      msg_quit = nil
+      if @error_count >= MAXIMUM_ERROR_COUNT
+        msg_quit = "too much errors, terminating"
+      elsif @fatal_error
+        msg_quit = "previous fatal error"
+      end
+
+      unless msg_quit.nil?
+        send_quit_decline msg_quit
+        close_connection_after_writing
+      end
+    end
+
     def new_thread(name, id = nil)
       id ||= @next_thread_id
       th = ConversationThread.new(self, id, name)
@@ -411,5 +483,9 @@ module CyborgHood
     def identifier
       "unix_socket/#{@signature}"
     end
+
+    def capabilities
+      super + []
+    end
   end
 end
diff --git a/lib/cyborghood/cyborg/interface.rb b/lib/cyborghood/cyborg/interface.rb
index 9f92fbd..b83021f 100644
--- a/lib/cyborghood/cyborg/interface.rb
+++ b/lib/cyborghood/cyborg/interface.rb
@@ -47,6 +47,10 @@ module CyborgHood
       def export_parent_methods
         self.export_method *self.superclass.public_instance_methods(false)
       end
+
+      def is_node?(node)
+        (node =~ NODE_PATTERN) ? true : false
+      end
     end
 
     def initialize(*args)
@@ -58,6 +62,11 @@ module CyborgHood
       self.class.auto_export_public_instance_methods = true
     end
 
+    # convenience method
+    def is_node?(node)
+      self.class.is_node?(node)
+    end
+
     def api_klasses
       list = self.class.constants.collect do |c|
         cc = self.class.const_get(c)
@@ -146,27 +155,16 @@ module CyborgHood
     # preliminary incoming message handling
     def call(session, cmd, data)
       action = find_node_action(session, cmd)
-      return "551 unknown node" if action.nil?
+      raise "unknown node" if action.nil?
 
-      if data.nil?
-        formated_data = []
-      else
-        begin
-          formated_data = YAML.load(data) unless data.nil?
-        rescue
-          return "552 unreadable YAML data for arguments"
-        end
-
-        return "552 wrong format for arguments" unless formated_data.is_a? Array
-      end
+      raise "wrong format for arguments" unless data.is_a? Array
 
       begin
-        # preliminary outgoing message handling
-        action.call(*formated_data).to_yaml
+        action.call(*data)
       rescue
         logger.debug "node action error message: " + $!
         logger.debug "node action error backtrace: " + $!.backtrace.join("\n")
-        return "550 method call failed: " + $!
+        raise "method call failed: " + $!
       end
     end
   end
diff --git a/lib/cyborghood/cyborg/server.rb b/lib/cyborghood/cyborg/server.rb
index fac4788..8749f1e 100644
--- a/lib/cyborghood/cyborg/server.rb
+++ b/lib/cyborghood/cyborg/server.rb
@@ -63,6 +63,10 @@ module CyborgHood
     def unregister_communication(peer)
       @comm_list.delete(peer)
     end
+
+    def schedule_task(callback, &task)
+      EventMachine.defer(task, callback)
+    end
   end
 
   class BotServerUNIXSocket < BotServer
