Project

General

Profile

« Previous | Next » 

Revision a445b722

Added by Marc Dequènes almost 14 years ago

  • ID a445b722dd8aefe49872e7748e41c333a48a4b34

[cleanup] the revenge of Mr Proper

View differences:

IDEAS
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:
bin/clerk
# to allow in-place run for test
$: << File.join(File.dirname(__FILE__), "..", "lib")
require 'cyborghood/cyborg'
require 'cyborghood'
module CyborgHood
bin/librarian
# to allow in-place run for test
$: << File.join(File.dirname(__FILE__), "..", "lib")
require 'cyborghood/cyborg'
require 'cyborghood'
module CyborgHood
bin/mapmaker
# to allow in-place run for test
$: << File.join(File.dirname(__FILE__), "..", "lib")
require 'cyborghood/cyborg'
require 'cyborghood'
module CyborgHood
bin/postman
# 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
......
define_interface "0.1~"
end
include I18nTranslation
def start_work
self.services.imap.check_mails do |msg|
begin
bin/test_client
# 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
......
end
end
bot = CyborgHood::TestClientHome::TestClientClient.new
bot = CyborgHood::TestClientLand::TestClientClient.new
trap('INT') do
bot.stop(:quickly)
lib/cyborghood-clerk/land.rb
def register_services(container)
container.namespace_define(:clerk_land) do |b|
b.records do
require 'cyborghood-clerk/records'
Records.new
end
end
end
lib/cyborghood-postman/land.rb
# 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
lib/cyborghood.rb
$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)
......
end
module_function :register_services
autoload :Cyborg, 'cyborghood/cyborg'
end
lib/cyborghood/base/i18n.rb
#--
# 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
lib/cyborghood/base/info.rb
#--
# 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
lib/cyborghood/base/lang_additions.rb
#--
# 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
lib/cyborghood/base/language.rb
#--
# 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
lib/cyborghood/base/ruby_extra.rb
#--
# 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
lib/cyborghood/cyborg.rb
# 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
lib/cyborghood/cyborg/botnet.rb
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
lib/cyborghood/cyborg/botnet/conversation.rb
require 'yaml'
require 'cyborghood/cyborg/botnet/session'
require 'cyborghood/cyborg/botnet/protocol'
require 'set'
module CyborgHood
lib/cyborghood/cyborg/botnet/dsl.rb
#--
# 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
lib/cyborghood/cyborg/botnet/interface.rb
# 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)
lib/cyborghood/cyborg/botnet/task.rb
#--
# 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
lib/cyborghood/cyborg/dsl.rb
#--
# 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
... This diff was truncated because it exceeds the maximum size that can be displayed.

Also available in: Unified diff