Revision a445b722
Added by Marc Dequènes over 13 years ago
- ID a445b722dd8aefe49872e7748e41c333a48a4b34
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
|
Also available in: Unified diff
[cleanup] the revenge of Mr Proper