commit a445b722dd8aefe49872e7748e41c333a48a4b34
Author: Marc Dequènes (Duck) <Duck@DuckCorp.org>
Date:   Wed Mar 9 01:54:31 2011 +0100

    [cleanup] the revenge of Mr Proper

diff --git a/IDEAS b/IDEAS
index cd21278..2a41bda 100644
--- a/IDEAS
+++ b/IDEAS
@@ -2,6 +2,12 @@
 misc:
   - rename schedule_task to schedule_job, as the <task> argument is not a DSL::Task
     or transform it into a real task
+  - remove I18nTranslation includes in the core to be able to unload i18n.rb
+    completely when no i18n support is needed in this cyborg
+  - could help get the bot class/model before it is instanciated
+      b.cyborg_model do
+        raise CyberError.new(:grave, "cyborg_init", "no model defined for this cyborg")
+      end
 
 all bots:
   - API:
diff --git a/bin/clerk b/bin/clerk
index b949e63..8b5b2c1 100755
--- a/bin/clerk
+++ b/bin/clerk
@@ -21,7 +21,7 @@
 # to allow in-place run for test
 $: << File.join(File.dirname(__FILE__), "..", "lib")
 
-require 'cyborghood/cyborg'
+require 'cyborghood'
 
 
 module CyborgHood
diff --git a/bin/librarian b/bin/librarian
index e62368f..be179e4 100755
--- a/bin/librarian
+++ b/bin/librarian
@@ -21,7 +21,7 @@
 # to allow in-place run for test
 $: << File.join(File.dirname(__FILE__), "..", "lib")
 
-require 'cyborghood/cyborg'
+require 'cyborghood'
 
 
 module CyborgHood
diff --git a/bin/mapmaker b/bin/mapmaker
index 5d72b26..0916e8c 100755
--- a/bin/mapmaker
+++ b/bin/mapmaker
@@ -21,7 +21,7 @@
 # to allow in-place run for test
 $: << File.join(File.dirname(__FILE__), "..", "lib")
 
-require 'cyborghood/cyborg'
+require 'cyborghood'
 
 
 module CyborgHood
diff --git a/bin/postman b/bin/postman
index e84cde9..512465c 100755
--- a/bin/postman
+++ b/bin/postman
@@ -21,9 +21,7 @@
 # to allow in-place run for test
 $: << File.join(File.dirname(__FILE__), "..", "lib")
 
-require 'cyborghood/cyborg'
-require 'cyborghood-postman/mail'
-require 'cyborghood-postman/mail_order'
+require 'cyborghood'
 
 
 module CyborgHood
@@ -42,8 +40,6 @@ module CyborgHood
         define_interface "0.1~"
       end
 
-      include I18nTranslation
-
       def start_work
         self.services.imap.check_mails do |msg|
           begin
diff --git a/bin/test_client b/bin/test_client
index 6821b3c..859a5a0 100755
--- a/bin/test_client
+++ b/bin/test_client
@@ -21,11 +21,11 @@
 # to allow in-place run for test
 $: << File.join(File.dirname(__FILE__), "..", "lib")
 
-require 'cyborghood/cyborg'
+require 'cyborghood'
 
 
 module CyborgHood
-  module TestClientHome
+  module TestClientLand
     add_translations 'testclient'
 
     class TestClientClient < Cyborg
@@ -96,7 +96,7 @@ module CyborgHood
   end
 end
 
-bot = CyborgHood::TestClientHome::TestClientClient.new
+bot = CyborgHood::TestClientLand::TestClientClient.new
 
 trap('INT') do
   bot.stop(:quickly)
diff --git a/lib/cyborghood-clerk/land.rb b/lib/cyborghood-clerk/land.rb
index b28a7cd..f5aff12 100644
--- a/lib/cyborghood-clerk/land.rb
+++ b/lib/cyborghood-clerk/land.rb
@@ -23,10 +23,6 @@ module CyborgHood
 
     def register_services(container)
       container.namespace_define(:clerk_land) do |b|
-        b.records do
-          require 'cyborghood-clerk/records'
-          Records.new
-        end
       end
     end
 
diff --git a/lib/cyborghood-postman/land.rb b/lib/cyborghood-postman/land.rb
index 678439f..0f0be53 100644
--- a/lib/cyborghood-postman/land.rb
+++ b/lib/cyborghood-postman/land.rb
@@ -19,6 +19,9 @@
 # ensure we can find the needed programs (should be handled somewhere else)
 ENV['PATH'] = (ENV['PATH'].split(":") + ["/sbin", "/usr/sbin", "/usr/local/sbin"]).uniq.join(":")
 
+require 'cyborghood-postman/mail'
+require 'cyborghood-postman/mail_order'
+
 
 module CyborgHood
   module PostmanLand
diff --git a/lib/cyborghood.rb b/lib/cyborghood.rb
index db02f44..eceb500 100644
--- a/lib/cyborghood.rb
+++ b/lib/cyborghood.rb
@@ -19,18 +19,22 @@
 $KCODE = 'UTF8'
 require 'jcode'
 require 'pp'
+require 'set'
+require 'ostruct'
 require 'needle'
+require 'cyborghood/base/exceptions'
 require 'cyborghood/base/config'
-require 'cyborghood/base/info'
-require 'cyborghood/base/lang_additions'
+require 'cyborghood/base/ruby_extra'
 require 'cyborghood/base/logger'
-require 'cyborghood/base/language'
-require 'cyborghood/base/exceptions'
+require 'cyborghood/base/i18n'
 
 
 ENV['LC_ALL'] = "C"
 
 module CyborgHood
+  PRODUCT = "CyborgHood"
+  VERSION = "0.4.0"
+
   add_deferred_translations
 
   def register_services(container)
@@ -46,4 +50,6 @@ module CyborgHood
   end
 
   module_function :register_services
+
+  autoload :Cyborg, 'cyborghood/cyborg'
 end
diff --git a/lib/cyborghood/base/i18n.rb b/lib/cyborghood/base/i18n.rb
new file mode 100644
index 0000000..f0ff0ee
--- /dev/null
+++ b/lib/cyborghood/base/i18n.rb
@@ -0,0 +1,181 @@
+#--
+# CyborgHood, a distributed system management software.
+# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
+#
+# 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'
+require 'gettext'
+require 'http_headers'
+
+
+# helpers
+class Module
+  include GetText
+
+  @@ch_translations = Set.new
+
+  def add_translations(domain)
+    if @@ch_translations.include? domain
+      textdomain(domain)
+    else
+      bindtextdomain(domain, {:path => CyborgHood::Config::L10N_DIR, :output_charset => "UTF-8"})
+      @@ch_translations << domain
+    end
+  end
+
+  def add_deferred_translations(cyborg_model = nil)
+    domain = 'cyborghood'
+    domain += '_' + cyborg_model if cyborg_model
+    add_translations(domain)
+
+    include CyborgHood::I18nTranslation
+  end
+end
+
+module CyborgHood
+  class I18nController
+    include Singleton
+    include GetText
+
+    DEFAULT_LOCALE = "en"
+
+    def initialize
+      @config = Config.instance
+
+      @available_locales = nil
+      @prefs_to_locale_cache = {}
+      @translate_mutex = Mutex.new
+    end
+
+    def available_locales
+      return @available_locales if @available_locales
+
+      list = ['en'] + Dir.new(Config::L10N_DIR).select{|d| File.directory?(File.join(Config::L10N_DIR, d)) and d[0..0] != "." }
+      # local admin can restrict available locales
+      # (may be useful if l10n is partial due to third party plugins)
+      list = list & @config.i18n.restricted_locale_set if @config.i18n.restricted_locale_set
+      list = ['en'] if list.empty?
+      @available_locales = list
+    end
+
+    def set_locale(locale)
+      if locale
+        set_locale_all(locale)
+      else
+        set_default_locale
+      end
+    end
+
+    def set_default_locale
+      set_locale(DEFAULT_LOCALE)
+    end
+
+    def locale_from_prefs(preferred_locales)
+      return DEFAULT_LOCALE if preferred_locales.nil?
+
+      locale = @prefs_to_locale_cache[preferred_locales]
+      return locale if locale
+
+      lang_chooser = HTTPHeaders::AcceptLanguage.parse(preferred_locales)
+      if lang_chooser
+        ordered_list = lang_chooser.reduce(available_locales)
+        locale = ordered_list.empty? ? DEFAULT_LOCALE : ordered_list.first.range
+        @prefs_to_locale_cache[preferred_locales] = locale
+      else
+        # notify the preferred_locales string is not a valid RFC2616 Accept-Language value
+        nil
+      end
+    end
+
+    def translate(message, preferred_locales)
+      locale = locale_from_prefs(preferred_locales)
+
+      @translate_mutex.synchronize do
+        set_locale(locale)
+        message.to_s
+      end
+    end
+  end
+
+  module I18nTranslation
+    def self.included(base)
+      base.class_eval("include GetText")
+
+      # translation methods needs to be available at class level too
+      base.extend(self)
+    end
+    include GetText
+
+    alias :_orig :_
+    def _(message, parts = {})
+      create_tm(message, parts, :_orig)
+    end
+
+    alias :n_orig :n_
+    def n_(message, parts = {})
+      create_tm(message, parts, :n_orig)
+    end
+
+    alias :s_orig :s_
+    def s_(message, parts = {})
+      create_tm(message, parts, :s_orig)
+    end
+
+    alias :ns_orig :ns_
+    def ns_(message, parts = {})
+      create_tm(message, parts, :ns_orig)
+    end
+
+    alias :np_orig :np_
+    def np_(message, parts = {})
+      create_tm(message, parts, :np_orig)
+    end
+
+    def tm_(message, parts = {})
+      create_tm(message, parts)
+    end
+
+    def create_tm(message, parts, method = nil)
+      I18nMessage.new(message, parts, self, method)
+    end
+
+    module_function :_, :n_, :s_, :ns_, :np_
+  end
+
+  class I18nMessage
+    include I18nTranslation
+
+    attr_reader :untranslated, :parts
+
+    def initialize(untranslated, parts, context, method)
+      @untranslated = untranslated
+      @parts = parts
+      @context = context
+      @method = method
+    end
+
+    def to_s
+      ch_i18n_msg_method, ch_i18n_msg_untranslated, ch_i18n_msg_parts = @method, @untranslated, @parts
+      @context.instance_eval do
+        send(ch_i18n_msg_method, ch_i18n_msg_untranslated) % ch_i18n_msg_parts
+      end
+    end
+
+    def translate(preferred_locales)
+      I18nController.instance.translate(self, preferred_locales)
+    end
+  end
+end
diff --git a/lib/cyborghood/base/info.rb b/lib/cyborghood/base/info.rb
deleted file mode 100644
index 25f179b..0000000
--- a/lib/cyborghood/base/info.rb
+++ /dev/null
@@ -1,22 +0,0 @@
-#--
-# CyborgHood, a distributed system management software.
-# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
-#
-# 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/>.
-#++
-
-module CyborgHood
-  PRODUCT = "CyborgHood"
-  VERSION = "0.4.0"
-end
diff --git a/lib/cyborghood/base/lang_additions.rb b/lib/cyborghood/base/lang_additions.rb
deleted file mode 100644
index 7454ae5..0000000
--- a/lib/cyborghood/base/lang_additions.rb
+++ /dev/null
@@ -1,73 +0,0 @@
-#--
-# CyborgHood, a distributed system management software.
-# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
-#
-# 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 'active_support'
-require 'ostruct'
-
-# backported fix: https://github.com/rails/rails/commit/c401102a2702f9b945803e66d3a25b77d882ee13
-class String
-  def html_safe?
-    defined?(@_rails_html_safe)
-  end
-
-  def concat_with_safety(other_or_fixnum)
-    result = concat_without_safety(other_or_fixnum)
-    unless html_safe? && also_html_safe?(other_or_fixnum)
-      remove_instance_variable(:@_rails_html_safe) if defined?(@_rails_html_safe)
-    end
-    result
-  end
-
-  undef_method :<<
-  alias_method :<<, :concat_with_safety
-end
-
-class Hash
-  def to_ostruct
-    data = self.dup
-    data.each_pair do |k, v|
-      data[k] = v.to_ostruct if v.is_a?(Hash)
-    end
-    OpenStruct.new(data)
-  end
-end
-
-class OpenStruct
-  def to_hash
-    @table.dup
-  end
-end
-
-class Object
-  def logger
-    CyborgHood::Logger.instance
-  end
-
-  def self.human_name
-    self.name.split("::").last
-  end
-  def human_name
-    self.class.human_name
-  end
-end
-
-class String
-  def is_numeric?
-    Float self rescue false
-  end
-end
diff --git a/lib/cyborghood/base/language.rb b/lib/cyborghood/base/language.rb
deleted file mode 100644
index f0ff0ee..0000000
--- a/lib/cyborghood/base/language.rb
+++ /dev/null
@@ -1,181 +0,0 @@
-#--
-# CyborgHood, a distributed system management software.
-# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
-#
-# 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'
-require 'gettext'
-require 'http_headers'
-
-
-# helpers
-class Module
-  include GetText
-
-  @@ch_translations = Set.new
-
-  def add_translations(domain)
-    if @@ch_translations.include? domain
-      textdomain(domain)
-    else
-      bindtextdomain(domain, {:path => CyborgHood::Config::L10N_DIR, :output_charset => "UTF-8"})
-      @@ch_translations << domain
-    end
-  end
-
-  def add_deferred_translations(cyborg_model = nil)
-    domain = 'cyborghood'
-    domain += '_' + cyborg_model if cyborg_model
-    add_translations(domain)
-
-    include CyborgHood::I18nTranslation
-  end
-end
-
-module CyborgHood
-  class I18nController
-    include Singleton
-    include GetText
-
-    DEFAULT_LOCALE = "en"
-
-    def initialize
-      @config = Config.instance
-
-      @available_locales = nil
-      @prefs_to_locale_cache = {}
-      @translate_mutex = Mutex.new
-    end
-
-    def available_locales
-      return @available_locales if @available_locales
-
-      list = ['en'] + Dir.new(Config::L10N_DIR).select{|d| File.directory?(File.join(Config::L10N_DIR, d)) and d[0..0] != "." }
-      # local admin can restrict available locales
-      # (may be useful if l10n is partial due to third party plugins)
-      list = list & @config.i18n.restricted_locale_set if @config.i18n.restricted_locale_set
-      list = ['en'] if list.empty?
-      @available_locales = list
-    end
-
-    def set_locale(locale)
-      if locale
-        set_locale_all(locale)
-      else
-        set_default_locale
-      end
-    end
-
-    def set_default_locale
-      set_locale(DEFAULT_LOCALE)
-    end
-
-    def locale_from_prefs(preferred_locales)
-      return DEFAULT_LOCALE if preferred_locales.nil?
-
-      locale = @prefs_to_locale_cache[preferred_locales]
-      return locale if locale
-
-      lang_chooser = HTTPHeaders::AcceptLanguage.parse(preferred_locales)
-      if lang_chooser
-        ordered_list = lang_chooser.reduce(available_locales)
-        locale = ordered_list.empty? ? DEFAULT_LOCALE : ordered_list.first.range
-        @prefs_to_locale_cache[preferred_locales] = locale
-      else
-        # notify the preferred_locales string is not a valid RFC2616 Accept-Language value
-        nil
-      end
-    end
-
-    def translate(message, preferred_locales)
-      locale = locale_from_prefs(preferred_locales)
-
-      @translate_mutex.synchronize do
-        set_locale(locale)
-        message.to_s
-      end
-    end
-  end
-
-  module I18nTranslation
-    def self.included(base)
-      base.class_eval("include GetText")
-
-      # translation methods needs to be available at class level too
-      base.extend(self)
-    end
-    include GetText
-
-    alias :_orig :_
-    def _(message, parts = {})
-      create_tm(message, parts, :_orig)
-    end
-
-    alias :n_orig :n_
-    def n_(message, parts = {})
-      create_tm(message, parts, :n_orig)
-    end
-
-    alias :s_orig :s_
-    def s_(message, parts = {})
-      create_tm(message, parts, :s_orig)
-    end
-
-    alias :ns_orig :ns_
-    def ns_(message, parts = {})
-      create_tm(message, parts, :ns_orig)
-    end
-
-    alias :np_orig :np_
-    def np_(message, parts = {})
-      create_tm(message, parts, :np_orig)
-    end
-
-    def tm_(message, parts = {})
-      create_tm(message, parts)
-    end
-
-    def create_tm(message, parts, method = nil)
-      I18nMessage.new(message, parts, self, method)
-    end
-
-    module_function :_, :n_, :s_, :ns_, :np_
-  end
-
-  class I18nMessage
-    include I18nTranslation
-
-    attr_reader :untranslated, :parts
-
-    def initialize(untranslated, parts, context, method)
-      @untranslated = untranslated
-      @parts = parts
-      @context = context
-      @method = method
-    end
-
-    def to_s
-      ch_i18n_msg_method, ch_i18n_msg_untranslated, ch_i18n_msg_parts = @method, @untranslated, @parts
-      @context.instance_eval do
-        send(ch_i18n_msg_method, ch_i18n_msg_untranslated) % ch_i18n_msg_parts
-      end
-    end
-
-    def translate(preferred_locales)
-      I18nController.instance.translate(self, preferred_locales)
-    end
-  end
-end
diff --git a/lib/cyborghood/base/ruby_extra.rb b/lib/cyborghood/base/ruby_extra.rb
new file mode 100644
index 0000000..3cd0548
--- /dev/null
+++ b/lib/cyborghood/base/ruby_extra.rb
@@ -0,0 +1,72 @@
+#--
+# CyborgHood, a distributed system management software.
+# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
+#
+# 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 'active_support'
+
+# backported fix: https://github.com/rails/rails/commit/c401102a2702f9b945803e66d3a25b77d882ee13
+class String
+  def html_safe?
+    defined?(@_rails_html_safe)
+  end
+
+  def concat_with_safety(other_or_fixnum)
+    result = concat_without_safety(other_or_fixnum)
+    unless html_safe? && also_html_safe?(other_or_fixnum)
+      remove_instance_variable(:@_rails_html_safe) if defined?(@_rails_html_safe)
+    end
+    result
+  end
+
+  undef_method :<<
+  alias_method :<<, :concat_with_safety
+end
+
+class Hash
+  def to_ostruct
+    data = self.dup
+    data.each_pair do |k, v|
+      data[k] = v.to_ostruct if v.is_a?(Hash)
+    end
+    OpenStruct.new(data)
+  end
+end
+
+class OpenStruct
+  def to_hash
+    @table.dup
+  end
+end
+
+class Object
+  def logger
+    CyborgHood::Logger.instance
+  end
+
+  def self.human_name
+    self.name.split("::").last
+  end
+  def human_name
+    self.class.human_name
+  end
+end
+
+class String
+  def is_numeric?
+    Float self rescue false
+  end
+end
diff --git a/lib/cyborghood/cyborg.rb b/lib/cyborghood/cyborg.rb
index 14be593..4513c65 100644
--- a/lib/cyborghood/cyborg.rb
+++ b/lib/cyborghood/cyborg.rb
@@ -16,25 +16,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #++
 
-require 'cyborghood'
 require 'socket'
 require 'eventmachine'
 require 'thread'
-require 'cyborghood/cyborg/dsl'
+require 'cyborghood/cyborg/task'
 
 
 module CyborgHood
-  module TaskAspect
-    def task(name, &block)
-      the_bot = self.is_a?(Cyborg) ? self : self.bot
-      DSL::Task.new(the_bot, name, &block)
-    end
-
-    def schedule_task(callback = nil, &task)
-      EventMachine.defer(task, callback)
-    end
-  end
-
   class Cyborg
     include TaskAspect
 
diff --git a/lib/cyborghood/cyborg/botnet.rb b/lib/cyborghood/cyborg/botnet.rb
index 98f8aeb..a9e1d87 100644
--- a/lib/cyborghood/cyborg/botnet.rb
+++ b/lib/cyborghood/cyborg/botnet.rb
@@ -19,8 +19,7 @@
 require 'cyborghood/cyborg'
 require 'cyborghood/cyborg/botnet/interface'
 require 'cyborghood/cyborg/botnet/conversation'
-require 'cyborghood/cyborg/botnet/dsl'
-require 'set'
+require 'cyborghood/cyborg/botnet/task'
 
 module CyborgHood
   module BotNet
diff --git a/lib/cyborghood/cyborg/botnet/conversation.rb b/lib/cyborghood/cyborg/botnet/conversation.rb
index cdddb10..6378209 100644
--- a/lib/cyborghood/cyborg/botnet/conversation.rb
+++ b/lib/cyborghood/cyborg/botnet/conversation.rb
@@ -19,7 +19,6 @@
 require 'yaml'
 require 'cyborghood/cyborg/botnet/session'
 require 'cyborghood/cyborg/botnet/protocol'
-require 'set'
 
 
 module CyborgHood
diff --git a/lib/cyborghood/cyborg/botnet/dsl.rb b/lib/cyborghood/cyborg/botnet/dsl.rb
deleted file mode 100644
index 1975121..0000000
--- a/lib/cyborghood/cyborg/botnet/dsl.rb
+++ /dev/null
@@ -1,135 +0,0 @@
-#--
-# CyborgHood, a distributed system management software.
-# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
-#
-# 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/>.
-#++
-
-
-module CyborgHood
-  module DSL
-    module BotnetTask
-      def ask(peer, key, cmd, *args)
-        action_name = ['ask', cmd, *args].hash
-
-        _add_subtask_using_peer(action_name, peer) do |subtask, conv_thread, env|
-          cmd = cmd.call if cmd.is_a? Proc
-          args.collect!{|a| a.is_a?(Proc) ? a.call : a }
-
-          conv_thread.call(env, 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[:exceptions]
-            end
-
-            subtask.finish
-          end
-        end
-      end
-
-      def know?(peer, key, cmd)
-        action_name = ['know', cmd].hash
-
-        _add_subtask_using_peer(action_name, peer) do |subtask, conv_thread, env|
-          cmd = cmd.call if cmd.is_a? Proc
-
-          conv_thread.exists?(env, cmd) do |reply|
-            case reply[:status]
-            when :ok
-              subtask.results = {key => reply[:result]}
-            when :decline
-              # TODO: remove this case ???
-            when :error
-              subtask.errors += reply[:exceptions]
-            end
-
-            subtask.finish
-          end
-        end
-      end
-
-      def _add_subtask_using_peer(action_name, peer)
-        subtask_name = "botnet/peer/#{peer}/#{action_name}/out"
-
-        _add_subtask(subtask_name) do |subtask|
-          logger.debug "Task '#{@name}': Trying to contact peer '#{peer}'"
-
-          # callback to end peer action and subtask
-          # (used when shooting the task)
-          defuse_peer_action_cb = Proc.new do
-            logger.debug "Task '#{@name}': defusing subtask '#{subtask.name}'"
-
-            subtask.finish unless subtask.finished?
-
-            # we already own the mutex here
-            registered_resources.delete(subtask_name)
-          end
-          # register peer action in the task
-          tasks_info_mutex.synchronize do
-            registered_resources[subtask_name] = defuse_peer_action_cb
-          end
-
-          # catch current environment to transmit it
-          env = {
-            :preferred_locales  => @preferred_locales,
-            :user               => @user
-          }
-
-          @bot.contact_peer(peer) do |conv|
-            if conv
-              logger.debug "Task '#{@name}': subtask '#{subtask.name}': peer '#{peer}' contacted, starting conversation"
-
-              @peer_contacted << peer
-
-              # don't use the block call to leave the conversation thread open
-              conv_thread = conv.thread(@notification_name)
-
-              yield(subtask, conv_thread, env)
-            else
-              logger.debug "Task '#{@name}': Could not contact peer '#{peer}'"
-              subtask.errors << CyberError.new(:unrecoverable, "botnet/client/dsl", "Task '#{@name}': could not contact peer '#{peer}'")
-              subtask.finish
-            end
-          end
-        end
-      end
-
-      def _setup
-        super
-
-        @peer_contacted = Set.new
-      end
-
-      def _finished
-        super
-
-        # close opened thread
-        cb = Proc.new do |conv|
-          conv.close_thread(@notification_name)
-        end
-
-        # loop on all contacted peers to close the thread
-        @peer_contacted.each do |peer|
-          @bot.contact_peer(peer, true, &cb)
-        end
-      end
-    end
-
-    Task.class_eval("include BotnetTask")
-  end
-end
diff --git a/lib/cyborghood/cyborg/botnet/interface.rb b/lib/cyborghood/cyborg/botnet/interface.rb
index d968a10..44de97a 100644
--- a/lib/cyborghood/cyborg/botnet/interface.rb
+++ b/lib/cyborghood/cyborg/botnet/interface.rb
@@ -16,15 +16,13 @@
 # along with this program.  If not, see <http://www.gnu.org/licenses/>.
 #++
 
-require 'singleton'
-require 'ostruct'
-
 
 module CyborgHood
   module DSL
-    class ServerApiNode < BaseDSL
+    class ServerApiNode
       attr_reader :bot, :node_name, :parent_node, :store
 
+      include I18nTranslation
       include TaskAspect
 
       def initialize(bot, parent_node = nil, options = {}, &block)
diff --git a/lib/cyborghood/cyborg/botnet/task.rb b/lib/cyborghood/cyborg/botnet/task.rb
new file mode 100644
index 0000000..1975121
--- /dev/null
+++ b/lib/cyborghood/cyborg/botnet/task.rb
@@ -0,0 +1,135 @@
+#--
+# CyborgHood, a distributed system management software.
+# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
+#
+# 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/>.
+#++
+
+
+module CyborgHood
+  module DSL
+    module BotnetTask
+      def ask(peer, key, cmd, *args)
+        action_name = ['ask', cmd, *args].hash
+
+        _add_subtask_using_peer(action_name, peer) do |subtask, conv_thread, env|
+          cmd = cmd.call if cmd.is_a? Proc
+          args.collect!{|a| a.is_a?(Proc) ? a.call : a }
+
+          conv_thread.call(env, 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[:exceptions]
+            end
+
+            subtask.finish
+          end
+        end
+      end
+
+      def know?(peer, key, cmd)
+        action_name = ['know', cmd].hash
+
+        _add_subtask_using_peer(action_name, peer) do |subtask, conv_thread, env|
+          cmd = cmd.call if cmd.is_a? Proc
+
+          conv_thread.exists?(env, cmd) do |reply|
+            case reply[:status]
+            when :ok
+              subtask.results = {key => reply[:result]}
+            when :decline
+              # TODO: remove this case ???
+            when :error
+              subtask.errors += reply[:exceptions]
+            end
+
+            subtask.finish
+          end
+        end
+      end
+
+      def _add_subtask_using_peer(action_name, peer)
+        subtask_name = "botnet/peer/#{peer}/#{action_name}/out"
+
+        _add_subtask(subtask_name) do |subtask|
+          logger.debug "Task '#{@name}': Trying to contact peer '#{peer}'"
+
+          # callback to end peer action and subtask
+          # (used when shooting the task)
+          defuse_peer_action_cb = Proc.new do
+            logger.debug "Task '#{@name}': defusing subtask '#{subtask.name}'"
+
+            subtask.finish unless subtask.finished?
+
+            # we already own the mutex here
+            registered_resources.delete(subtask_name)
+          end
+          # register peer action in the task
+          tasks_info_mutex.synchronize do
+            registered_resources[subtask_name] = defuse_peer_action_cb
+          end
+
+          # catch current environment to transmit it
+          env = {
+            :preferred_locales  => @preferred_locales,
+            :user               => @user
+          }
+
+          @bot.contact_peer(peer) do |conv|
+            if conv
+              logger.debug "Task '#{@name}': subtask '#{subtask.name}': peer '#{peer}' contacted, starting conversation"
+
+              @peer_contacted << peer
+
+              # don't use the block call to leave the conversation thread open
+              conv_thread = conv.thread(@notification_name)
+
+              yield(subtask, conv_thread, env)
+            else
+              logger.debug "Task '#{@name}': Could not contact peer '#{peer}'"
+              subtask.errors << CyberError.new(:unrecoverable, "botnet/client/dsl", "Task '#{@name}': could not contact peer '#{peer}'")
+              subtask.finish
+            end
+          end
+        end
+      end
+
+      def _setup
+        super
+
+        @peer_contacted = Set.new
+      end
+
+      def _finished
+        super
+
+        # close opened thread
+        cb = Proc.new do |conv|
+          conv.close_thread(@notification_name)
+        end
+
+        # loop on all contacted peers to close the thread
+        @peer_contacted.each do |peer|
+          @bot.contact_peer(peer, true, &cb)
+        end
+      end
+    end
+
+    Task.class_eval("include BotnetTask")
+  end
+end
diff --git a/lib/cyborghood/cyborg/dsl.rb b/lib/cyborghood/cyborg/dsl.rb
deleted file mode 100644
index ce69985..0000000
--- a/lib/cyborghood/cyborg/dsl.rb
+++ /dev/null
@@ -1,463 +0,0 @@
-#--
-# CyborgHood, a distributed system management software.
-# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
-#
-# 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 'active_support/basic_object'
-require 'set'
-
-
-module CyborgHood
-  module DSL
-    class BaseDSL
-      include I18nTranslation
-    end
-
-    class TaskBase < BaseDSL
-      attr_reader :bot, :name, :errors, :results, :preferred_locales, :locale, :user
-
-      # mutex used for all class variables in this paragraph
-      @@tasks_info_mutex = Mutex.new
-      @@task_wip = 0
-      @@registered_resources = {}
-
-      @@stop_all_tasks = false
-
-      def self.idle?
-        @@tasks_info_mutex.synchronize do
-          @@task_wip == 0
-        end
-      end
-
-      def self.stop_all
-        @@stop_all_tasks = true
-
-        # call defuse callback out of the sync block
-        # (which may need to synchronize later to the
-        #  same mutex in usual conditions)
-        resources_to_free = true
-        while not resources_to_free.nil?
-          @@tasks_info_mutex.synchronize do
-            resources_to_free = @@registered_resources.shift
-          end
-          resources_to_free[1].call unless resources_to_free.nil?
-        end
-      end
-
-      # the name MUST be unique
-      def initialize(bot, name, &block)
-        @bot = bot
-        @name = name
-
-        @@tasks_info_mutex.synchronize do
-          @@task_wip += 1
-        end
-
-        @errors = {}.freeze
-        @results = {}.freeze
-
-        @notification_name = "task/#{@name}"
-        @preferred_locales = nil
-        @locale = nil
-        @user = nil
-
-        _setup
-        _start_dsl &block
-      end
-
-      def set_preferred_locales(prefs)
-        _add_subtask("setting/preferred_locales") do |subtask|
-          logger.debug "Task '#{@name}': setting preferred locales to: #{prefs}"
-          @preferred_locales = prefs
-
-          subtask.finish
-        end
-      end
-
-      # temporary setting until Guard is created
-      def set_user(user)
-        _add_subtask("setting/user") do |subtask|
-          logger.debug "Task '#{@name}': setting user to: #{user}"
-          @user = user
-
-          subtask.finish
-        end
-      end
-
-      # may return a Hash of results
-      def schedule(&job)
-        _add_subtask("job/#{job.hash}") do |subtask|
-          logger.debug "Task '#{@name}': Scheduling job"
-          cb = Proc.new do
-            subtask.finish
-          end
-          job_wrapper = Proc.new do
-            job.call subtask
-          end
-          @bot.schedule_task(cb, job_wrapper)
-        end
-      end
-
-      def send_notification(name, data)
-        name = @notification_name if name == :task
-
-        _add_subtask("notification/#{name}/out") do |subtask|
-          logger.debug "Task '#{@name}': Sending notification to '#{name}'"
-          chan = @bot.get_channel(name)
-          chan << data
-          subtask.finish
-        end
-      end
-
-      def wait_notification(name, criterias = {}, timeout = nil,  &cb)
-        name = @notification_name if name == :task
-
-        subtask_name = "notification/#{name}/#{criterias.hash}/in"
-        _add_subtask(subtask_name) do |subtask|
-          logger.debug "Task '#{@name}': subtask '#{subtask.name}': waiting notification on '#{name}'"
-          subcription_id = nil
-          subcription_id_mutex = Mutex.new
-
-          # callback to end notification and subtask
-          # (used when shooting the task)
-          defuse_notification_cb = Proc.new do
-            logger.debug "Task '#{@name}': defusing subtask '#{subtask.name}'"
-
-            subcription_id_mutex.synchronize do
-              @bot.get_channel(name).unsubscribe(subcription_id) unless subcription_id.nil?
-              subcription_id = nil
-            end
-
-            subtask.finish unless subtask.finished?
-
-            # we already own the mutex here
-            @@registered_resources.delete(subtask_name)
-          end
-          # register notification in the task
-          @@tasks_info_mutex.synchronize do
-            @@registered_resources[subtask_name] = defuse_notification_cb
-          end
-
-          subcription_id = @bot.get_channel(name).subscribe do |msg|
-            if _notification_criterias_match(msg, criterias)
-              cb.call(subtask, msg)
-
-              if subtask.finished?
-                subcription_id_mutex.synchronize do
-                  @bot.get_channel(name).unsubscribe(subcription_id) unless subcription_id.nil?
-                  subcription_id = nil
-                end
-
-                # unregister the notification
-                @@tasks_info_mutex.synchronize do
-                  @@registered_resources.delete(subtask_name)
-                end
-              end
-            end
-          end
-
-          if timeout
-            EventMachine.add_timer(timeout) do
-              subcription_id_mutex.synchronize do
-                @bot.get_channel(name).unsubscribe(subcription_id) unless subcription_id.nil?
-                subcription_id = nil
-              end
-
-              # unregister the notification
-              @@tasks_info_mutex.synchronize do
-                @@registered_resources.delete(subtask_name)
-              end
-
-              subtask.finish
-            end
-          end
-        end
-      end
-
-      def wait_timer(timeout, repeat = false, &cb)
-        subtask_name = "timer/#{timeout}/#{cb.hash}"
-        _add_subtask(subtask_name) do |subtask|
-          timer_signature = nil
-          timer_signature_mutex = Mutex.new
-
-          # callback to end timer and subtask
-          # (used when shooting the task)
-          defuse_timer_cb = Proc.new do
-            logger.debug "Task '#{@name}': defusing subtask '#{subtask.name}'"
-
-            timer_signature_mutex.synchronize do
-              EventMachine.cancel_timer(timer_signature) unless timer_signature.nil?
-              timer_signature = nil
-            end
-
-            subtask.finish unless subtask.finished?
-
-            # we already own the mutex here
-            @@registered_resources.delete(subtask_name)
-          end
-          # register timer in the task
-          @@tasks_info_mutex.synchronize do
-            @@registered_resources[subtask_name] = defuse_timer_cb
-          end
-
-          timer_cb = Proc.new do
-            if repeat
-              cb.call(subtask)
-              timer_signature_mutex.synchronize do
-                if subtask.finished? and not timer_signature.nil?
-                  EventMachine.cancel_timer(timer_signature)
-                  timer_signature = nil
-                end
-              end
-            else
-              cb.call(subtask)
-
-              # unregister the timer
-              @@tasks_info_mutex.synchronize do
-                @@registered_resources.delete(subtask_name)
-              end
-
-              subtask.finish unless subtask.finished?
-            end
-          end
-
-          if repeat
-            timer_signature = EventMachine.add_periodic_timer(timeout, timer_cb)
-          else
-            timer_signature = EventMachine.add_timer(timeout, timer_cb)
-          end
-        end
-      end
-
-      # the name MUST be unique
-      def task(name, &block)
-        _add_subtask("task/#{name}") do |subtask|
-          self.class.new(@bot, name, &block)
-          subtask.finish
-        end
-      end
-
-      def meet(task_list, meetpoint, retry_time = 2)
-        task_list = [task_list] unless task_list.is_a? Array
-        task_list = Set.new(task_list)
-
-        notification_name = "meeting_point/#{meetpoint}"
-        notifications_received = Set.new
-        meet_ok = false
-
-        wait_notification(notification_name, {:topic => "MEET"}) do |subtask, msg|
-          notifications_received << msg[:from] if task_list.include?(msg[:from])
-          if task_list == notifications_received
-            meet_ok = true
-            subtask.finish
-          end
-        end
-        wait_timer(retry_time, true) do |subtask|
-          # in this order: send a last message before leaving
-          # (so we are sure the peer received a reply to its message)
-          send_notification notification_name, {:topic => "MEET", :from => @name}
-
-          subtask.finish if meet_ok or @@stop_all_tasks
-        end
-      end
-
-      # error when adding subtasks, as we cannot check callbacks
-      # only applies to subsequently added tasks (wanted behavior)
-      def cancel_on_start_error
-        @cancel_on_start_error = true
-      end
-
-      def on_error(&cb)
-        @error_cb = cb
-      end
-
-      def on_success(&cb)
-        @success_cb = cb
-      end
-
-      def stop_bot(condition)
-        _add_subtask('stop') do |subtask|
-          @bot.stop(condition)
-
-          subtask.finish
-        end
-      end
-
-      protected
-
-      class Subtask
-        attr_reader :name
-        attr_accessor :results, :errors
-
-        def initialize(task, name, &block)
-          @task = task
-          @name = name
-          @block = block
-
-          @finished = false
-          @results = {}
-          @errors = []
-        end
-
-        def do_it
-          @block.call self
-        rescue
-          msg = "Task '#{@task.name}': error: " + $!.to_s
-          logger.error msg
-          @errors << CyberError.new(:unrecoverable, "botnet/client/dsl", msg)
-          logger.debug $!.backtrace.join("\n")
-          finish
-        end
-
-        def finish
-          if @finished
-            raise CyberError.new(:unrecoverable, "botnet/client/dsl", "Task '#{@task.name}': subtask '#{@name}' should have ended, but it lied")
-          end
-          @finished = true
-          logger.debug "Task '#{@task.name}': subtask '#{@name}' finished"
-          @task.__send__(:_check_finished)
-        end
-
-        def finished?
-          @finished
-        end
-      end
-
-      def _add_subtask(name, cb = nil, &block)
-        subtask = Subtask.new(self, name, &block)
-        @subtasks << subtask
-        subtask.do_it if @dsl_runing
-      end
-
-      def _subtasks_finished?
-        subtasks_running = []
-        @subtasks.each do |subtask|
-          subtasks_running << subtask.name unless subtask.finished?
-        end
-
-        logger.debug "Task '#{@name}': the following subtasks are still running: " +
-          subtasks_running.join(', ') unless subtasks_running.empty?
-
-        subtasks_running.empty?
-      end
-
-      def _check_finished
-        return unless _subtasks_finished?
-
-        # avoid race: no subtask will be run in this task now
-        @dsl_runing = false
-
-        logger.debug "Task '#{@name}': step finished"
-
-        if @@stop_all_tasks
-          _finished
-
-          # running no more steps will end the task
-          return
-        end
-
-        # compute step result
-        @errors = {}
-        @results = {}
-        @subtasks.each do |subtask|
-          @errors[subtask.name] = subtask.errors unless subtask.errors.empty?
-          @results.merge!(subtask.results)
-        end
-        @errors.freeze
-        @result.freeze
-
-        # next step
-        cb = @errors.empty? ? @success_cb : @error_cb
-        if cb
-          _start_dsl(&cb)
-        else
-          _finished
-        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 _setup
-        logger.debug "Task '#{@name}': created"
-      end
-
-      def _start_dsl(&block)
-        @dsl_runing = false
-        @subtasks = []
-        @cancel_on_start_error = false
-        @error_cb = nil
-        @success_cb = nil
-
-        begin
-          instance_eval(&block)
-        rescue
-          logger.error "Task '#{@name}': error in DSL: " + $!.to_s
-          logger.debug $!.backtrace.join("\n")
-          return
-        end
-
-        logger.debug "Task '#{@name}': begining step"
-
-        if @subtasks.empty?
-          _check_finished
-        else
-          @dsl_runing = true
-          @subtasks.each do |subtask|
-            # skip broken or canceled subtasks
-            next if subtask.finished?
-            subtask.do_it
-          end
-        end
-
-        true
-      end
-
-      def _finished
-        @@tasks_info_mutex.synchronize do
-          @@task_wip -= 1
-          logger.debug "Task '#{@name}': finished (#{@@task_wip} tasks remaining)"
-        end
-      end
-
-      # needed for mixin
-      # TODO: find a cleaner solution
-      def tasks_info_mutex
-        @@tasks_info_mutex
-      end
-
-      # needed for mixin
-      # TODO: find a cleaner solution
-      def registered_resources
-        @@registered_resources
-      end
-    end
-
-    # this empty class is used as a trick to be able to inject features
-    # as module.prepend does not exist yet
-    class Task < TaskBase
-    end
-  end
-end
diff --git a/lib/cyborghood/cyborg/task.rb b/lib/cyborghood/cyborg/task.rb
new file mode 100644
index 0000000..897a17b
--- /dev/null
+++ b/lib/cyborghood/cyborg/task.rb
@@ -0,0 +1,469 @@
+#--
+# CyborgHood, a distributed system management software.
+# Copyright (c) 2009-2011 Marc Dequènes (Duck) <Duck@DuckCorp.org>
+#
+# 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/>.
+#++
+
+
+module CyborgHood
+  module TaskAspect
+    def task(name, &block)
+      the_bot = self.is_a?(Cyborg) ? self : self.bot
+      DSL::Task.new(the_bot, name, &block)
+    end
+
+    def schedule_task(callback = nil, &task)
+      EventMachine.defer(task, callback)
+    end
+  end
+
+  module DSL
+    class TaskBase
+      attr_reader :bot, :name, :errors, :results, :preferred_locales, :locale, :user
+
+      include I18nTranslation
+
+      # mutex used for all class variables in this paragraph
+      @@tasks_info_mutex = Mutex.new
+      @@task_wip = 0
+      @@registered_resources = {}
+
+      @@stop_all_tasks = false
+
+      def self.idle?
+        @@tasks_info_mutex.synchronize do
+          @@task_wip == 0
+        end
+      end
+
+      def self.stop_all
+        @@stop_all_tasks = true
+
+        # call defuse callback out of the sync block
+        # (which may need to synchronize later to the
+        #  same mutex in usual conditions)
+        resources_to_free = true
+        while not resources_to_free.nil?
+          @@tasks_info_mutex.synchronize do
+            resources_to_free = @@registered_resources.shift
+          end
+          resources_to_free[1].call unless resources_to_free.nil?
+        end
+      end
+
+      # the name MUST be unique
+      def initialize(bot, name, &block)
+        @bot = bot
+        @name = name
+
+        @@tasks_info_mutex.synchronize do
+          @@task_wip += 1
+        end
+
+        @errors = {}.freeze
+        @results = {}.freeze
+
+        @notification_name = "task/#{@name}"
+        @preferred_locales = nil
+        @locale = nil
+        @user = nil
+
+        _setup
+        _start_dsl &block
+      end
+
+      def set_preferred_locales(prefs)
+        _add_subtask("setting/preferred_locales") do |subtask|
+          logger.debug "Task '#{@name}': setting preferred locales to: #{prefs}"
+          @preferred_locales = prefs
+
+          subtask.finish
+        end
+      end
+
+      # temporary setting until Guard is created
+      def set_user(user)
+        _add_subtask("setting/user") do |subtask|
+          logger.debug "Task '#{@name}': setting user to: #{user}"
+          @user = user
+
+          subtask.finish
+        end
+      end
+
+      # may return a Hash of results
+      def schedule(&job)
+        _add_subtask("job/#{job.hash}") do |subtask|
+          logger.debug "Task '#{@name}': Scheduling job"
+          cb = Proc.new do
+            subtask.finish
+          end
+          job_wrapper = Proc.new do
+            job.call subtask
+          end
+          @bot.schedule_task(cb, job_wrapper)
+        end
+      end
+
+      def send_notification(name, data)
+        name = @notification_name if name == :task
+
+        _add_subtask("notification/#{name}/out") do |subtask|
+          logger.debug "Task '#{@name}': Sending notification to '#{name}'"
+          chan = @bot.get_channel(name)
+          chan << data
+          subtask.finish
+        end
+      end
+
+      def wait_notification(name, criterias = {}, timeout = nil,  &cb)
+        name = @notification_name if name == :task
+
+        subtask_name = "notification/#{name}/#{criterias.hash}/in"
+        _add_subtask(subtask_name) do |subtask|
+          logger.debug "Task '#{@name}': subtask '#{subtask.name}': waiting notification on '#{name}'"
+          subcription_id = nil
+          subcription_id_mutex = Mutex.new
+
+          # callback to end notification and subtask
+          # (used when shooting the task)
+          defuse_notification_cb = Proc.new do
+            logger.debug "Task '#{@name}': defusing subtask '#{subtask.name}'"
+
+            subcription_id_mutex.synchronize do
+              @bot.get_channel(name).unsubscribe(subcription_id) unless subcription_id.nil?
+              subcription_id = nil
+            end
+
+            subtask.finish unless subtask.finished?
+
+            # we already own the mutex here
+            @@registered_resources.delete(subtask_name)
+          end
+          # register notification in the task
+          @@tasks_info_mutex.synchronize do
+            @@registered_resources[subtask_name] = defuse_notification_cb
+          end
+
+          subcription_id = @bot.get_channel(name).subscribe do |msg|
+            if _notification_criterias_match(msg, criterias)
+              cb.call(subtask, msg)
+
+              if subtask.finished?
+                subcription_id_mutex.synchronize do
+                  @bot.get_channel(name).unsubscribe(subcription_id) unless subcription_id.nil?
+                  subcription_id = nil
+                end
+
+                # unregister the notification
+                @@tasks_info_mutex.synchronize do
+                  @@registered_resources.delete(subtask_name)
+                end
+              end
+            end
+          end
+
+          if timeout
+            EventMachine.add_timer(timeout) do
+              subcription_id_mutex.synchronize do
+                @bot.get_channel(name).unsubscribe(subcription_id) unless subcription_id.nil?
+                subcription_id = nil
+              end
+
+              # unregister the notification
+              @@tasks_info_mutex.synchronize do
+                @@registered_resources.delete(subtask_name)
+              end
+
+              subtask.finish
+            end
+          end
+        end
+      end
+
+      def wait_timer(timeout, repeat = false, &cb)
+        subtask_name = "timer/#{timeout}/#{cb.hash}"
+        _add_subtask(subtask_name) do |subtask|
+          timer_signature = nil
+          timer_signature_mutex = Mutex.new
+
+          # callback to end timer and subtask
+          # (used when shooting the task)
+          defuse_timer_cb = Proc.new do
+            logger.debug "Task '#{@name}': defusing subtask '#{subtask.name}'"
+
+            timer_signature_mutex.synchronize do
+              EventMachine.cancel_timer(timer_signature) unless timer_signature.nil?
+              timer_signature = nil
+            end
+
+            subtask.finish unless subtask.finished?
+
+            # we already own the mutex here
+            @@registered_resources.delete(subtask_name)
+          end
+          # register timer in the task
+          @@tasks_info_mutex.synchronize do
+            @@registered_resources[subtask_name] = defuse_timer_cb
+          end
+
+          timer_cb = Proc.new do
+            if repeat
+              cb.call(subtask)
+              timer_signature_mutex.synchronize do
+                if subtask.finished? and not timer_signature.nil?
+                  EventMachine.cancel_timer(timer_signature)
+                  timer_signature = nil
+                end
+              end
+            else
+              cb.call(subtask)
+
+              # unregister the timer
+              @@tasks_info_mutex.synchronize do
+                @@registered_resources.delete(subtask_name)
+              end
+
+              subtask.finish unless subtask.finished?
+            end
+          end
+
+          if repeat
+            timer_signature = EventMachine.add_periodic_timer(timeout, timer_cb)
+          else
+            timer_signature = EventMachine.add_timer(timeout, timer_cb)
+          end
+        end
+      end
+
+      # the name MUST be unique
+      def task(name, &block)
+        _add_subtask("task/#{name}") do |subtask|
+          self.class.new(@bot, name, &block)
+          subtask.finish
+        end
+      end
+
+      def meet(task_list, meetpoint, retry_time = 2)
+        task_list = [task_list] unless task_list.is_a? Array
+        task_list = Set.new(task_list)
+
+        notification_name = "meeting_point/#{meetpoint}"
+        notifications_received = Set.new
+        meet_ok = false
+
+        wait_notification(notification_name, {:topic => "MEET"}) do |subtask, msg|
+          notifications_received << msg[:from] if task_list.include?(msg[:from])
+          if task_list == notifications_received
+            meet_ok = true
+            subtask.finish
+          end
+        end
+        wait_timer(retry_time, true) do |subtask|
+          # in this order: send a last message before leaving
+          # (so we are sure the peer received a reply to its message)
+          send_notification notification_name, {:topic => "MEET", :from => @name}
+
+          subtask.finish if meet_ok or @@stop_all_tasks
+        end
+      end
+
+      # error when adding subtasks, as we cannot check callbacks
+      # only applies to subsequently added tasks (wanted behavior)
+      def cancel_on_start_error
+        @cancel_on_start_error = true
+      end
+
+      def on_error(&cb)
+        @error_cb = cb
+      end
+
+      def on_success(&cb)
+        @success_cb = cb
+      end
+
+      def stop_bot(condition)
+        _add_subtask('stop') do |subtask|
+          @bot.stop(condition)
+
+          subtask.finish
+        end
+      end
+
+      protected
+
+      class Subtask
+        attr_reader :name
+        attr_accessor :results, :errors
+
+        def initialize(task, name, &block)
+          @task = task
+          @name = name
+          @block = block
+
+          @finished = false
+          @results = {}
+          @errors = []
+        end
+
+        def do_it
+          @block.call self
+        rescue
+          msg = "Task '#{@task.name}': error: " + $!.to_s
+          logger.error msg
+          @errors << CyberError.new(:unrecoverable, "botnet/client/dsl", msg)
+          logger.debug $!.backtrace.join("\n")
+          finish
+        end
+
+        def finish
+          if @finished
+            raise CyberError.new(:unrecoverable, "botnet/client/dsl", "Task '#{@task.name}': subtask '#{@name}' should have ended, but it lied")
+          end
+          @finished = true
+          logger.debug "Task '#{@task.name}': subtask '#{@name}' finished"
+          @task.__send__(:_check_finished)
+        end
+
+        def finished?
+          @finished
+        end
+      end
+
+      def _add_subtask(name, cb = nil, &block)
+        subtask = Subtask.new(self, name, &block)
+        @subtasks << subtask
+        subtask.do_it if @dsl_runing
+      end
+
+      def _subtasks_finished?
+        subtasks_running = []
+        @subtasks.each do |subtask|
+          subtasks_running << subtask.name unless subtask.finished?
+        end
+
+        logger.debug "Task '#{@name}': the following subtasks are still running: " +
+          subtasks_running.join(', ') unless subtasks_running.empty?
+
+        subtasks_running.empty?
+      end
+
+      def _check_finished
+        return unless _subtasks_finished?
+
+        # avoid race: no subtask will be run in this task now
+        @dsl_runing = false
+
+        logger.debug "Task '#{@name}': step finished"
+
+        if @@stop_all_tasks
+          _finished
+
+          # running no more steps will end the task
+          return
+        end
+
+        # compute step result
+        @errors = {}
+        @results = {}
+        @subtasks.each do |subtask|
+          @errors[subtask.name] = subtask.errors unless subtask.errors.empty?
+          @results.merge!(subtask.results)
+        end
+        @errors.freeze
+        @result.freeze
+
+        # next step
+        cb = @errors.empty? ? @success_cb : @error_cb
+        if cb
+          _start_dsl(&cb)
+        else
+          _finished
+        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 _setup
+        logger.debug "Task '#{@name}': created"
+      end
+
+      def _start_dsl(&block)
+        @dsl_runing = false
+        @subtasks = []
+        @cancel_on_start_error = false
+        @error_cb = nil
+        @success_cb = nil
+
+        begin
+          instance_eval(&block)
+        rescue
+          logger.error "Task '#{@name}': error in DSL: " + $!.to_s
+          logger.debug $!.backtrace.join("\n")
+          return
+        end
+
+        logger.debug "Task '#{@name}': begining step"
+
+        if @subtasks.empty?
+          _check_finished
+        else
+          @dsl_runing = true
+          @subtasks.each do |subtask|
+            # skip broken or canceled subtasks
+            next if subtask.finished?
+            subtask.do_it
+          end
+        end
+
+        true
+      end
+
+      def _finished
+        @@tasks_info_mutex.synchronize do
+          @@task_wip -= 1
+          logger.debug "Task '#{@name}': finished (#{@@task_wip} tasks remaining)"
+        end
+      end
+
+      # needed for mixin
+      # TODO: find a cleaner solution
+      def tasks_info_mutex
+        @@tasks_info_mutex
+      end
+
+      # needed for mixin
+      # TODO: find a cleaner solution
+      def registered_resources
+        @@registered_resources
+      end
+    end
+
+    # this empty class is used as a trick to be able to inject features
+    # as module.prepend does not exist yet
+    class Task < TaskBase
+    end
+  end
+end
