commit 848944dcce9d9a0f132a21d2ca01a8824a34e5b4
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Fri Nov 26 02:24:12 2010 +0100

    [evol] Task-based client DSL §6 (added botnet 'ask' subtask)

diff --git a/bin/test_client b/bin/test_client
index 9ef7b85..d5b96c0 100755
--- a/bin/test_client
+++ b/bin/test_client
@@ -40,21 +40,10 @@ module CyborgHood
         # to test the closure through the inner instance_eval calls
         toto = true
 
-        c = 0
         task "compare stuff" do
-#          ask :MapMaker, :ver1, "/api_version"
-#          ask :Librarian, :ver2, "/api_version"
-          wait_notification :task, {:topic => "HELOz"}, 5 do |subtask, msg|
-            puts "POUET NOTIF"
-            subtask.finish
-          end
-          send_notification :task, {:topic => "HELO"}
-          wait_timer 2, true do |subtask|
-            c += 1
-            puts "TIMER !!!"
-            STDOUT.flush
-            subtask.finish if c == 10
-          end
+          ask "MapMaker", :ver1, "/api_version"
+          ask "Librarian", :ver2, "/api_version"
+          ask "MapMaker", :zones, "/DNS/Zones"
           on_error do
             puts "PLOUF"
             pp errors
@@ -64,7 +53,7 @@ module CyborgHood
             puts "OK"
             pp errors
             pp results
-#            puts "Tadam: " + (ver1 == ver2 ? "same" : "different")
+            puts "Tadam: " + (results[:ver1] == results[:ver2] ? "same" : "different")
             meet "waiter", :zzz
             STDOUT.flush
             on_success do
@@ -77,53 +66,55 @@ module CyborgHood
           meet "compare stuff", :zzz
           on_success do
             puts "OK waiter"
+            #stop_bot :at_once
+            stop_bot :when_finished
           end
         end
 
-        conversation_with "MapMaker" do
-          on_error do
-            puts "Halalalala !"
-          end
-          thread "super taiste" do
-            call :ver, "/api_version"
-            call :zones, "/DNS/Zones" if toto
-            on_error do
-              puts "Sniff !"
-              pp reply
-            end
-            on_success do
-              puts "Yahou !"
-              pp reply
-              call :gogogo, "/DNS"
-              on_error do
-                pp "Plouf"
-                pp reply
-              end
-              on_success do
-                pp "Hop!"
-                pp reply
-                send_notification 'meetpoint', { :topic => "MYNOTIF", :msg => "plop" }
-              end
-            end
-          end
-          #stop_bot :at_once
-          stop_bot :when_finished
-        end
+        # conversation_with "MapMaker" do
+        #   on_error do
+        #     puts "Halalalala !"
+        #   end
+        #   thread "super taiste" do
+        #     call :ver, "/api_version"
+        #     call :zones, "/DNS/Zones" if toto
+        #     on_error do
+        #       puts "Sniff !"
+        #       pp reply
+        #     end
+        #     on_success do
+        #       puts "Yahou !"
+        #       pp reply
+        #       call :gogogo, "/DNS"
+        #       on_error do
+        #         pp "Plouf"
+        #         pp reply
+        #       end
+        #       on_success do
+        #         pp "Hop!"
+        #         pp reply
+        #         send_notification 'meetpoint', { :topic => "MYNOTIF", :msg => "plop" }
+        #       end
+        #     end
+        #   end
+        #   #stop_bot :at_once
+        #   stop_bot :when_finished
+        # end
 
-        conversation_with "Librarian" do
-          thread "taistouille" do
-            call :sdf, "/Gruik"
-            on_success do
-              puts "Librarian GoGoGo!"
-              pp reply
-              wait_notification 'meetpoint', { :topic => "MYNOTIF" }
-              on_success do
-                puts "NOTIF!"
-                pp reply
-              end
-            end
-          end
-        end
+        # conversation_with "Librarian" do
+        #   thread "taistouille" do
+        #     call :sdf, "/Gruik"
+        #     on_success do
+        #       puts "Librarian GoGoGo!"
+        #       pp reply
+        #       wait_notification 'meetpoint', { :topic => "MYNOTIF" }
+        #       on_success do
+        #         puts "NOTIF!"
+        #         pp reply
+        #       end
+        #     end
+        #   end
+        # end
       end
     end
   end
diff --git a/lib/cyborghood/cyborg/botnet_dsl.rb b/lib/cyborghood/cyborg/botnet_dsl.rb
index ff93792..3dca49c 100644
--- a/lib/cyborghood/cyborg/botnet_dsl.rb
+++ b/lib/cyborghood/cyborg/botnet_dsl.rb
@@ -16,229 +16,44 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #++
 
-require 'active_support/basic_object'
-
 
 module CyborgHood
   module BotnetDSL
-    class BaseDSL < ActiveSupport::BasicObject
-
-      def self.run_dsl(*args, &block)
-        dsl = new(*args)
-        dsl.instance_eval(&block)
-        dsl.instance_eval do
-          _start_dsl
-        end
-      end
-    end
-
-    class Thread < BaseDSL
-      attr_reader :name, :conversation, :reply
-
-      def initialize(conversation_dsl, conversation, name, reply = nil)
-        @conversation_dsl = conversation_dsl
-        @conversation = conversation
-        @name = name
-        @reply = reply
-
-        @error_cb = nil
-        @success_cb = nil
-        @calls = {}
-        @calls_reply = {}
-        @in_notifications = {}
-        @in_notifications_awaiting = 0
-        @in_notifications_received = 0
-        @out_notifications = {}
-
-        @final_reply = {
-          :results        => {},
-          :errors         => {},
-          :notifications  => {}
-        }
-
-        @notification_name = "task/#{@name}"
-      end
-
-      def on_error(&callback)
-        @error_cb = callback
-      end
-
-      def on_success(&callback)
-        @success_cb = callback
-      end
-
-      def call(key, cmd, *args)
-        @calls[key] = {
-          :cmd   => cmd,
-          :args  => args
-        }
-      end
-
-      def send_notification(name, data)
-        name = @notification_name if name == :thread
-        Logger.instance.debug "Sending notification to '#{name}'"
-        @out_notifications[name] ||= []
-        @out_notifications[name] << data
-      end
-
-      # TODO: implement settable timeout
-      def wait_notification(name, criterias = {}, &callback)
-        name = @notification_name if name == :thread
-        @in_notifications[name] ||= []
-        @in_notifications[name] << {
-          :criterias => criterias,
-          :callback => callback
-        }
-        @in_notifications_awaiting += 1
-      end
-
-      protected
-
-      def _has_events?
-        not @calls.empty? or not @in_notifications.empty?
-      end
-
-      def _start_dsl
-        if _has_events?
-          @conversation.thread(@name) do |conv_thread|
-            conv_thread.lock(@name)
-            _waiting_events(conv_thread)
-          end
-        end
-
-        _acts
-      end
-
-      def _waiting_events(conv_thread)
-        @calls.each_pair do |key, data|
-          conv_thread.call(data[:cmd], *data[:args]) do |reply|
-            @calls_reply[key] = reply[:status]
-
-            case reply[:status]
-            when :ok
-              @final_reply[:results][key] = reply[:result]
-            when :decline
-              # TODO: remove this case ???
-            when :error
-              @final_reply[:errors][key] = reply[:exception]
-            end
-
-            _check_finished(conv_thread)
-          end
-        end
-
-        @in_notifications.each_pair do |name, list|
-          subcription_id = @conversation.bot.get_channel(name).subscribe do |msg|
-            old_notifications_received = @in_notifications_received
-            # TODO: remove events from the list when condition reached
-            list.each do |data|
-              if _notification_criterias_match(msg, data[:criterias])
-                stop_listening = true
-                if data[:callback]
-                  stop_listening = data[:callback].call(msg)
-                else
-                  # process notification later, in the on_* blocks
-                  @final_reply[:notifications][name] ||= []
-                  @final_reply[:notifications][name] << msg
+    module BotnetTask
+      def ask(peer, key, cmd, *args)
+        _add_subtask("botnet/peer/#{peer}/out") do |subtask|
+          logger.debug "Task '#{@name}': Trying to contact peer '#{peer}'"
+          @bot.contact_peer(peer) do |conv|
+            if conv
+              logger.debug "Task '#{@name}': Peer '#{peer}' contacted, starting conversation"
+
+              # don't use the block call to leave the conversation thread open
+              conv_thread = conv.thread(@notification_name)
+              conv_thread.call(cmd, *args) do |reply|
+                case reply[:status]
+                when :ok
+                  subtask.results = {key => reply[:result]}
+                when :decline
+                  # TODO: remove this case ???
+                when :error
+                  subtask.errors << reply[:exception]
                 end
 
-                @in_notifications_received += 1 if stop_listening
-                # do not break, the same message may satisfy multiple criterias
+                subtask.finish
               end
+            else
+              logger.debug "Task '#{@name}': Could not contact peer '#{peer}'"
+              # TODO: retry (wait_timer + recursive call + counter)
+              subtask.errors << CyberError.new(:unrecoverable, "botnet/client/dsl", "Task '#{@name}': could not contact peer '#{peer}'")
+              subtask.finish
             end
-            # TODO: condition is wrong in case of custom callback
-            if @final_reply[:notifications][name].size >= @in_notifications[name].size
-              @conversation.bot.get_channel(name).unsubscribe(subcription_id)
-            end
-            _check_finished(conv_thread) if @in_notifications_received > old_notifications_received
           end
         end
       end
-
-      def _acts
-        @out_notifications.each_pair do |name, list|
-          chan = @conversation.bot.get_channel(name)
-          list.each do |data|
-            chan << data
-          end
-        end
-      end
-
-      def _notification_criterias_match(msg, criterias)
-        criterias.each_pair do |key, expected_val|
-          val = msg[key]
-          if expected_val.is_a? Regexp
-            return false if val !~ expected_val
-          else
-            return false if val != expected_val
-          end
-        end
-        true
-      end
-
-      def _finished?
-        @calls.size == @calls_reply.size and
-          @in_notifications_received == @in_notifications_awaiting
-      end
-
-      def _check_finished(conv_thread)
-        return unless _finished?
-
-        conv_thread.unlock(@name)
-
-        # process reply in the same thread, but with a new DSL Thread context
-        cb = @calls_reply.values.index(:error) ? @error_cb : @success_cb
-        Thread.run_dsl(@conversation_dsl, @conversation, @name, @final_reply, &cb) if cb
-      end
     end
 
-    class Conversation < BaseDSL
-      attr_reader :bot, :peer
-
-      def initialize(bot, peer)
-        @bot = bot
-        @peer = peer
-
-        @threads = {}
-        @error_cb = nil
-        @stop_bot = nil
-      end
-
-      def on_error(&callback)
-        @error_cb = callback
-      end
-
-      def thread(name, &block)
-        @threads[name] = block
-      end
-
-      def stop_bot(condition)
-        @stop_bot = condition
-      end
-
-      protected
-
-      def _start_dsl
-        @bot.contact_peer(peer) do |conv|
-          if conv
-            @threads.each_pair do |name, block|
-              Thread.run_dsl(self, conv, name, &block)
-            end
-          else
-            @error_cb.call unless @error_cb.nil?
-          end
-
-          case @stop_bot
-          when :when_finished
-            # in contact loop, in order to wait for conversation registration to happen
-            # or the bot would stop before contact is done
-            @bot.try_stop
-          when :at_once
-            # it won't wait for conversation to finish...
-            @bot.stop
-          end
-        end
-      end
+    ::CyborgHood::DSL::Task.class_eval do
+      include BotnetTask
     end
   end
 end
