diff options
Diffstat (limited to 'lib')
-rw-r--r-- | lib/rbot/ircbot.rb | 17 | ||||
-rw-r--r-- | lib/rbot/plugins.rb | 2 | ||||
-rw-r--r-- | lib/rbot/registry.rb | 326 | ||||
-rw-r--r-- | lib/rbot/registry/daybreak.rb | 245 | ||||
-rw-r--r-- | lib/rbot/registry/dbm.rb | 244 | ||||
-rw-r--r-- | lib/rbot/registry/tc.rb | 44 |
6 files changed, 397 insertions, 481 deletions
diff --git a/lib/rbot/ircbot.rb b/lib/rbot/ircbot.rb index ea6a57c8..02414a07 100644 --- a/lib/rbot/ircbot.rb +++ b/lib/rbot/ircbot.rb @@ -153,6 +153,7 @@ require 'rbot/rfc2812' require 'rbot/ircsocket' require 'rbot/botuser' require 'rbot/timer' +require 'rbot/registry' require 'rbot/plugins' require 'rbot/message' require 'rbot/language' @@ -198,6 +199,9 @@ class Bot # mechanize agent factory attr_accessor :agent + # loads and opens new registry databases, used by the plugins + attr_accessor :registry_factory + # server we are connected to # TODO multiserver def server @@ -433,9 +437,9 @@ class Bot Config.register Config::StringValue.new('core.db', :default => "dbm", :wizard => true, :default => "dbm", - :validate => Proc.new { |v| ["dbm", "daybreak"].include? v }, + :validate => Proc.new { |v| Registry::formats.include? v }, :requires_restart => true, - :desc => "DB adaptor to use for storing the plugin data/registries. Options: dbm (included in ruby), daybreak") + :desc => "DB adaptor to use for storing the plugin data/registries. Options: " + Registry::formats.join(', ')) @argv = params[:argv] @run_dir = params[:run_dir] || Dir.pwd @@ -499,14 +503,7 @@ class Bot $daemonize = true end - case @config["core.db"] - when "dbm" - require 'rbot/registry/dbm' - when "daybreak" - require 'rbot/registry/daybreak' - else - raise _("Unknown DB adaptor: %s") % @config["core.db"] - end + @registry_factory = Registry.new @config['core.db'] @logfile = @config['log.file'] if @logfile.class!=String || @logfile.empty? diff --git a/lib/rbot/plugins.rb b/lib/rbot/plugins.rb index a05a5b8f..c499fd40 100644 --- a/lib/rbot/plugins.rb +++ b/lib/rbot/plugins.rb @@ -187,7 +187,7 @@ module Plugins @botmodule_triggers = Array.new @handler = MessageMapper.new(self) - @registry = Registry::Accessor.new(@bot, self.class.to_s.gsub(/^.*::/, "")) + @registry = @bot.registry_factory.create(@bot.path, self.class.to_s.gsub(/^.*::/, '')) @manager.add_botmodule(self) if self.respond_to?('set_language') diff --git a/lib/rbot/registry.rb b/lib/rbot/registry.rb new file mode 100644 index 00000000..6c18df21 --- /dev/null +++ b/lib/rbot/registry.rb @@ -0,0 +1,326 @@ +#-- vim:sw=2:et +#++ +# +# :title: Registry: Persistent storage interface and factory +# +# This class provides persistent storage for plugins via a hash interface. +# The default mode is an object store, so you can store ruby objects and +# reference them with hash keys. This is because the default store/restore +# methods of the plugins' RegistryAccessor are calls to Marshal.dump and +# Marshal.restore, +# for example: +# blah = Hash.new +# blah[:foo] = "fum" +# @registry[:blah] = blah +# then, even after the bot is shut down and disconnected, on the next run you +# can access the blah object as it was, with: +# blah = @registry[:blah] +# The registry can of course be used to store simple strings, fixnums, etc as +# well, and should be useful to store or cache plugin data or dynamic plugin +# configuration. +# +# WARNING: +# in object store mode, don't make the mistake of treating it like a live +# object, e.g. (using the example above) +# @registry[:blah][:foo] = "flump" +# will NOT modify the object in the registry - remember that Registry#[] +# returns a Marshal.restore'd object, the object you just modified in place +# will disappear. You would need to: +# blah = @registry[:blah] +# blah[:foo] = "flump" +# @registry[:blah] = blah +# +# If you don't need to store objects, and strictly want a persistant hash of +# strings, you can override the store/restore methods to suit your needs, for +# example (in your plugin): +# def initialize +# class << @registry +# def store(val) +# val +# end +# def restore(val) +# val +# end +# end +# end +# Your plugins section of the registry is private, it has its own namespace +# (derived from the plugin's class name, so change it and lose your data). +# Calls to registry.each etc, will only iterate over your namespace. + +module Irc +class Bot + +class Registry + + # Dynamically loads the specified registry type library. + def initialize(format=nil) + @libpath = File.join(File.dirname(__FILE__), 'registry') + @format = format + load File.join(@libpath, @format+'.rb') if format + end + + # Returns a list of supported registry database formats. + def discover + Dir.glob(File.join(@libpath, '*.rb')).map do |name| + File.basename(name, File.extname(name)) + end + end + + # Creates a new Accessor object for the specified database filename. + def create(path, filename) + # The get_impl method will return a list of all the classes that + # implement the accessor interface, since we only ever load one + # (the configured one) accessor implementation, we can just assume + # it to be the correct accessor to use. + cls = AbstractAccessor.get_impl.first + cls.new(File.join(path, 'registry_' + @format, filename.downcase)) + end + + # Helper method that will return a list of supported registry formats. + def self.formats + @@formats ||= Registry.new.discover + end + + # Abstract database accessor (a hash-like interface). + class AbstractAccessor + + # lets the user define a recovery procedure in case the Marshal + # deserialization fails, it might be manually recover data. + # NOTE: weird legacy stuff, used by markov plugin (WTH?) + attr_accessor :recovery + + def initialize(filename) + debug 'init registry accessor for: ' + filename + @filename = filename + @name = File.basename filename + @registry = nil + @default = nil + @recovery = nil + end + + def sub_registry(prefix) + path = File.join(@filename.gsub(/\.[^\/\.]+$/,''), prefix.to_s) + return self.class.new(path) + end + + # creates the registry / subregistry folders + def create_folders + debug 'create folders for: ' + @filename + dirs = File.dirname(@filename).split("/") + dirs.length.times { |i| + dir = dirs[0,i+1].join("/")+"/" + unless File.exist?(dir) + Dir.mkdir(dir) + end + } + end + + # Will return true if the database file exists. + def dbexists? + File.exists? @filename + end + + # convert value to string form for storing in the registry + # defaults to Marshal.dump(val) but you can override this in your module's + # registry object to use any method you like. + # For example, if you always just handle strings use: + # def store(val) + # val + # end + def store(val) + Marshal.dump(val) + end + + # restores object from string form, restore(store(val)) must return val. + # If you override store, you should override restore to reverse the + # action. + # For example, if you always just handle strings use: + # def restore(val) + # val + # end + def restore(val) + begin + Marshal.restore(val) + rescue Exception => e + error _("failed to restore marshal data for #{val.inspect}, attempting recovery or fallback to default") + debug e + if defined? @recovery and @recovery + begin + return @recovery.call(val) + rescue Exception => ee + error _("marshal recovery failed, trying default") + debug ee + end + end + return default + end + end + + # Returned instead of nil if key wasnt found. + def set_default (default) + @default = default + end + + def default + @default && (@default.dup rescue @default) + end + + # Opens the database (if not already open) for read/write access. + def registry + create_folders unless dbexists? + end + + # Forces flush/sync the database on disk. + def flush + return unless @registry + @registry.flush + end + + # Should optimize/vacuum the database. + def optimize + return unless @registry + @registry.optimize + end + + # Closes the database. + def close + return unless @registry + @registry.close + @registry = nil + end + + # lookup a key in the registry + def [](key) + if dbexists? and registry.has_key?(key.to_s) + return restore(registry[key.to_s]) + else + return default + end + end + + # set a key in the registry + def []=(key,value) + registry[key.to_s] = store(value) + end + + # like Hash#each + def each(&block) + return nil unless dbexists? + registry.each do |key| + block.call(key, self[key]) + end + end + + alias each_pair each + + # like Hash#each_key + def each_key(&block) + self.each do |key| + block.call(key) + end + end + + # like Hash#each_value + def each_value(&block) + self.each do |key, value| + block.call(value) + end + end + + # just like Hash#has_key? + def has_key?(key) + return nil unless dbexists? + return registry.has_key?(key.to_s) + end + + alias include? has_key? + alias member? has_key? + alias key? has_key? + + # just like Hash#has_value? + def has_value?(value) + return nil unless dbexists? + return registry.has_value?(store(value)) + end + + # just like Hash#index? + def index(value) + self.each do |k,v| + return k if v == value + end + return nil + end + + # delete a key from the registry + def delete(key) + return default unless dbexists? + return registry.delete(key.to_s) + end + + # returns a list of your keys + def keys + return [] unless dbexists? + return registry.keys + end + + # just like Hash#has_both? + def has_both?(key, value) + return false unless dbexists? + registry.has_key?(key.to_s) and registry.has_value?(store(value)) + end + + # Return an array of all associations [key, value] in your namespace + def to_a + return [] unless dbexists? + ret = Array.new + self.each {|key, value| + ret << [key, value] + } + return ret + end + + # Return an hash of all associations {key => value} in your namespace + def to_hash + return {} unless dbexists? + ret = Hash.new + self.each {|key, value| + ret[key] = value + } + return ret + end + + # empties the registry (restricted to your namespace) + def clear + return unless dbexists? + registry.clear + end + alias truncate clear + + # returns an array of the values in your namespace of the registry + def values + return [] unless dbexists? + ret = Array.new + self.each {|k,v| + ret << v + } + return ret + end + + # returns the number of keys in your registry namespace + def length + return 0 unless dbexists? + registry.length + end + alias size length + + # Returns all classes from the namespace that implement this interface + def self.get_impl + ObjectSpace.each_object(Class).select { |klass| klass < self } + end + end + +end # Registry + +end # Bot +end # Irc + diff --git a/lib/rbot/registry/daybreak.rb b/lib/rbot/registry/daybreak.rb index 5d995379..6058b408 100644 --- a/lib/rbot/registry/daybreak.rb +++ b/lib/rbot/registry/daybreak.rb @@ -1,10 +1,11 @@ #-- vim:sw=2:et #++ # -# Daybreak is a simple and very fast key value store for ruby. +# :title: Daybreak registry implementation +# +# Daybreak is a fast in-memory(!!!) database: # http://propublica.github.io/daybreak/ # -# :title: DB interface require 'daybreak' @@ -12,250 +13,22 @@ module Irc class Bot class Registry - # This class provides persistent storage for plugins via a hash interface. - # The default mode is an object store, so you can store ruby objects and - # reference them with hash keys. This is because the default store/restore - # methods of the plugins' RegistryAccessor are calls to Marshal.dump and - # Marshal.restore, - # for example: - # blah = Hash.new - # blah[:foo] = "fum" - # @registry[:blah] = blah - # then, even after the bot is shut down and disconnected, on the next run you - # can access the blah object as it was, with: - # blah = @registry[:blah] - # The registry can of course be used to store simple strings, fixnums, etc as - # well, and should be useful to store or cache plugin data or dynamic plugin - # configuration. - # - # WARNING: - # in object store mode, don't make the mistake of treating it like a live - # object, e.g. (using the example above) - # @registry[:blah][:foo] = "flump" - # will NOT modify the object in the registry - remember that Registry#[] - # returns a Marshal.restore'd object, the object you just modified in place - # will disappear. You would need to: - # blah = @registry[:blah] - # blah[:foo] = "flump" - # @registry[:blah] = blah - # - # If you don't need to store objects, and strictly want a persistant hash of - # strings, you can override the store/restore methods to suit your needs, for - # example (in your plugin): - # def initialize - # class << @registry - # def store(val) - # val - # end - # def restore(val) - # val - # end - # end - # end - # Your plugins section of the registry is private, it has its own namespace - # (derived from the plugin's class name, so change it and lose your data). - # Calls to registry.each etc, will only iterate over your namespace. - class Accessor - - attr_accessor :recovery + class DaybreakAccessor < AbstractAccessor - # plugins don't call this - a Registry::Accessor is created for them and - # is accessible via @registry. - def initialize(bot, name) - @bot = bot - @name = name.downcase - @filename = @bot.path 'registry_daybreak', @name+'.db' - dirs = File.dirname(@filename).split("/") - dirs.length.times { |i| - dir = dirs[0,i+1].join("/")+"/" - unless File.exist?(dir) - debug "creating subregistry directory #{dir}" - Dir.mkdir(dir) - end - } - @registry = nil - @default = nil - @recovery = nil - # debug "initializing registry accessor with name #{@name}" + def initialize(filename) + super filename + '.db' end def registry + super @registry ||= Daybreak::DB.new(@filename) end - def flush - return unless @registry - @registry.flush - end - - def close + def optimize return unless @registry - @registry.close - @registry = nil - end - - # convert value to string form for storing in the registry - # defaults to Marshal.dump(val) but you can override this in your module's - # registry object to use any method you like. - # For example, if you always just handle strings use: - # def store(val) - # val - # end - def store(val) - Marshal.dump(val) - end - - # restores object from string form, restore(store(val)) must return val. - # If you override store, you should override restore to reverse the - # action. - # For example, if you always just handle strings use: - # def restore(val) - # val - # end - def restore(val) - begin - Marshal.restore(val) - rescue Exception => e - error _("failed to restore marshal data for #{val.inspect}, attempting recovery or fallback to default") - debug e - if defined? @recovery and @recovery - begin - return @recovery.call(val) - rescue Exception => ee - error _("marshal recovery failed, trying default") - debug ee - end - end - return default - end - end - - # lookup a key in the registry - def [](key) - if registry.has_key?(key.to_s) - return restore(registry[key.to_s]) - else - return default - end - end - - # set a key in the registry - def []=(key,value) - registry[key.to_s] = store(value) - end - - # set the default value for registry lookups, if the key sought is not - # found, the default will be returned. The default default (har) is nil. - def set_default (default) - @default = default - end - - def default - @default && (@default.dup rescue @default) - end - - # like Hash#each - def each(&block) - registry.each do |key| - block.call(key, self[key]) - end - end - - alias each_pair each - - # like Hash#each_key - def each_key(&block) - registry.each do |key| - block.call(key) - end - end - - # like Hash#each_value - def each_value(&block) - registry.each do |key| - block.call(self[key]) - end + @registry.compact end - # just like Hash#has_key? - def has_key?(key) - return registry.has_key?(key.to_s) - end - - alias include? has_key? - alias member? has_key? - alias key? has_key? - - # just like Hash#has_both? - def has_both?(key, value) - registry.has_key?(key.to_s) and registry.has_value?(store(value)) - end - - # just like Hash#has_value? - def has_value?(value) - return registry.has_value?(store(value)) - end - - # just like Hash#index? - def index(value) - self.each do |k,v| - return k if v == value - end - return nil - end - - # delete a key from the registry - def delete(key) - return registry.delete(key.to_s) - end - - # returns a list of your keys - def keys - return registry.keys - end - - # Return an array of all associations [key, value] in your namespace - def to_a - ret = Array.new - registry.each {|key, value| - ret << [key, restore(value)] - } - return ret - end - - # Return an hash of all associations {key => value} in your namespace - def to_hash - ret = Hash.new - registry.each {|key, value| - ret[key] = restore(value) - } - return ret - end - - # empties the registry (restricted to your namespace) - def clear - registry.clear - end - alias truncate clear - - # returns an array of the values in your namespace of the registry - def values - ret = Array.new - self.each {|k,v| - ret << restore(v) - } - return ret - end - - def sub_registry(prefix) - return Accessor.new(@bot, @name + "/" + prefix.to_s) - end - - # returns the number of keys in your registry namespace - def length - registry.length - end - alias size length end end # Registry diff --git a/lib/rbot/registry/dbm.rb b/lib/rbot/registry/dbm.rb index a0676539..a13cb8ce 100644 --- a/lib/rbot/registry/dbm.rb +++ b/lib/rbot/registry/dbm.rb @@ -1,14 +1,14 @@ #-- vim:sw=2:et #++ # -# The DBM class of the ruby std-lib provides wrappers for Unix-style -# dbm or Database Manager libraries. The exact library used depends +# :title: DBM registry implementation +# +# DBM is the ruby standard library wrapper module for Unix-style +# dbm libraries. The specific library used depends # on how ruby was compiled. Its any of the following: ndbm, bdb, # gdbm or qdbm. -# DBM API Documentation: # http://ruby-doc.org/stdlib-2.1.0/libdoc/dbm/rdoc/DBM.html # -# :title: DB interface require 'dbm' @@ -16,74 +16,10 @@ module Irc class Bot class Registry - # This class provides persistent storage for plugins via a hash interface. - # The default mode is an object store, so you can store ruby objects and - # reference them with hash keys. This is because the default store/restore - # methods of the plugins' RegistryAccessor are calls to Marshal.dump and - # Marshal.restore, - # for example: - # blah = Hash.new - # blah[:foo] = "fum" - # @registry[:blah] = blah - # then, even after the bot is shut down and disconnected, on the next run you - # can access the blah object as it was, with: - # blah = @registry[:blah] - # The registry can of course be used to store simple strings, fixnums, etc as - # well, and should be useful to store or cache plugin data or dynamic plugin - # configuration. - # - # WARNING: - # in object store mode, don't make the mistake of treating it like a live - # object, e.g. (using the example above) - # @registry[:blah][:foo] = "flump" - # will NOT modify the object in the registry - remember that Registry#[] - # returns a Marshal.restore'd object, the object you just modified in place - # will disappear. You would need to: - # blah = @registry[:blah] - # blah[:foo] = "flump" - # @registry[:blah] = blah - # - # If you don't need to store objects, and strictly want a persistant hash of - # strings, you can override the store/restore methods to suit your needs, for - # example (in your plugin): - # def initialize - # class << @registry - # def store(val) - # val - # end - # def restore(val) - # val - # end - # end - # end - # Your plugins section of the registry is private, it has its own namespace - # (derived from the plugin's class name, so change it and lose your data). - # Calls to registry.each etc, will only iterate over your namespace. - class Accessor - - attr_accessor :recovery - - # plugins don't call this - a Registry::Accessor is created for them and - # is accessible via @registry. - def initialize(bot, name) - @bot = bot - @name = name.downcase - @filename = @bot.path 'registry_dbm', @name - dirs = File.dirname(@filename).split("/") - dirs.length.times { |i| - dir = dirs[0,i+1].join("/")+"/" - unless File.exist?(dir) - debug "creating subregistry directory #{dir}" - Dir.mkdir(dir) - end - } - @registry = nil - @default = nil - @recovery = nil - # debug "initializing registry accessor with name #{@name}" - end + class DBMAccessor < AbstractAccessor def registry + super @registry ||= DBM.open(@filename, 0666, DBM::WRCREAT) end @@ -94,174 +30,14 @@ class Registry registry end - def close - return if !@registry - registry.close - @registry = nil - end - - # convert value to string form for storing in the registry - # defaults to Marshal.dump(val) but you can override this in your module's - # registry object to use any method you like. - # For example, if you always just handle strings use: - # def store(val) - # val - # end - def store(val) - Marshal.dump(val) - end - - # restores object from string form, restore(store(val)) must return val. - # If you override store, you should override restore to reverse the - # action. - # For example, if you always just handle strings use: - # def restore(val) - # val - # end - def restore(val) - begin - Marshal.restore(val) - rescue Exception => e - error _("failed to restore marshal data for #{val.inspect}, attempting recovery or fallback to default") - debug e - if defined? @recovery and @recovery - begin - return @recovery.call(val) - rescue Exception => ee - error _("marshal recovery failed, trying default") - debug ee - end - end - return default - end - end - - # lookup a key in the registry - def [](key) - if registry.has_key?(key.to_s) - return restore(registry[key.to_s]) - else - return default - end - end - - # set a key in the registry - def []=(key,value) - registry[key.to_s] = store(value) - end - - # set the default value for registry lookups, if the key sought is not - # found, the default will be returned. The default default (har) is nil. - def set_default (default) - @default = default - end - - def default - @default && (@default.dup rescue @default) - end - - # like Hash#each - def each(&block) - registry.each_key do |key| - block.call(key, self[key]) - end - end - - alias each_pair each - - # like Hash#each_key - def each_key(&block) - registry.each_key do |key| - block.call(key) - end - end - - # like Hash#each_value - def each_value(&block) - registry.each_key do |key| - block.call(self[key]) - end - end - - # just like Hash#has_key? - def has_key?(key) - return registry.has_key?(key.to_s) - end - - alias include? has_key? - alias member? has_key? - alias key? has_key? - - # just like Hash#has_both? - def has_both?(key, value) - registry.has_key?(key.to_s) and registry.has_value?(store(value)) - end - - # just like Hash#has_value? - def has_value?(value) - return registry.has_value?(store(value)) - end - - # just like Hash#index? - def index(value) - self.each do |k,v| - return k if v == value - end - return nil - end - - # delete a key from the registry - def delete(key) - return registry.delete(key.to_s) + def dbexists? + not Dir.glob(@filename + '.*').empty? end - # returns a list of your keys - def keys - return registry.keys + def optimize + # unsupported! end - # Return an array of all associations [key, value] in your namespace - def to_a - ret = Array.new - registry.each {|key, value| - ret << [key, restore(value)] - } - return ret - end - - # Return an hash of all associations {key => value} in your namespace - def to_hash - ret = Hash.new - registry.each {|key, value| - ret[key] = restore(value) - } - return ret - end - - # empties the registry (restricted to your namespace) - def clear - registry.clear - end - alias truncate clear - - # returns an array of the values in your namespace of the registry - def values - ret = Array.new - self.each {|k,v| - ret << restore(v) - } - return ret - end - - def sub_registry(prefix) - return Accessor.new(@bot, @name + "/" + prefix.to_s) - end - - # returns the number of keys in your registry namespace - def length - registry.length - end - alias size length end end # Registry diff --git a/lib/rbot/registry/tc.rb b/lib/rbot/registry/tc.rb new file mode 100644 index 00000000..63bfdf78 --- /dev/null +++ b/lib/rbot/registry/tc.rb @@ -0,0 +1,44 @@ +#-- vim:sw=2:et +#++ +# +# :title: TokyoCabinet B+Tree registry implementation +# +# TokyoCabinet is a "modern implementation of the DBM". +# http://fallabs.com/tokyocabinet/ +# + +require 'tokyocabinet' + +module Irc +class Bot +class Registry + + class TokyoCabinetAccessor < AbstractAccessor + + def initialize(filename) + super filename + '.tdb' + end + + def registry + super + unless @registry + @registry = TokyoCabinet::BDB.new + @registry.open(@filename, + TokyoCabinet::BDB::OREADER | + TokyoCabinet::BDB::OCREAT | + TokyoCabinet::BDB::OWRITER) + end + @registry + end + + def flush + return unless @registry + @registry.sync + end + + end + +end # Registry +end # Bot +end # Irc + |