diff options
Diffstat (limited to 'rbot/registry.rb')
-rw-r--r-- | rbot/registry.rb | 294 |
1 files changed, 294 insertions, 0 deletions
diff --git a/rbot/registry.rb b/rbot/registry.rb new file mode 100644 index 00000000..751ec9a6 --- /dev/null +++ b/rbot/registry.rb @@ -0,0 +1,294 @@ +# Copyright (C) 2002 Tom Gilbert. +# +# Permission is hereby granted, free of charge, to any person obtaining a copy +# of this software and associated documentation files (the "Software"), to +# deal in the Software without restriction, including without limitation the +# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or +# sell copies of the Software, and to permit persons to whom the Software is +# furnished to do so, subject to the following conditions: +# +# The above copyright notice and this permission notice shall be included in +# all copies of the Software and its documentation and acknowledgment shall be +# given in the documentation and software packages that this Software was +# used. +# +# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL +# THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER +# IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN +# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +require 'rbot/dbhash' + +module Irc + + # this is the backend of the RegistryAccessor class, which ties it to a + # DBHash object called plugin_registry(.db). All methods are delegated to + # the DBHash. + class BotRegistry + def initialize(bot) + @bot = bot + upgrade_data + @db = DBTree.new @bot, "plugin_registry" + end + + # delegation hack + def method_missing(method, *args, &block) + @db.send(method, *args, &block) + end + + # check for older versions of rbot with data formats that require updating + # NB this function is called _early_ in init(), pretty much all you have to + # work with is @bot.botclass. + def upgrade_data + if File.exist?("#{@bot.botclass}/registry.db") + puts "upgrading old-style (rbot 0.9.5 or earlier) plugin registry to new format" + old = BDB::Hash.open "#{@bot.botclass}/registry.db", nil, + "r+", 0600, "set_pagesize" => 1024, + "set_cachesize" => [0, 32 * 1024, 0] + new = BDB::CIBtree.open "#{@bot.botclass}/plugin_registry.db", nil, + BDB::CREATE | BDB::EXCL | BDB::TRUNCATE, + 0600, "set_pagesize" => 1024, + "set_cachesize" => [0, 32 * 1024, 0] + old.each {|k,v| + new[k] = v + } + old.close + new.close + File.delete("#{@bot.botclass}/registry.db") + end + end + end + + # 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 BotRegistry#[] + # 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 BotRegistryAccessor + # plugins don't call this - a BotRegistryAccessor is created for them and + # is accessible via @registry. + def initialize(bot, prefix) + @bot = bot + @registry = @bot.registry + @orig_prefix = prefix + @prefix = prefix + "/" + @default = nil + # debug "initializing registry accessor with prefix #{@prefix}" + end + + # use this to chop up your namespace into bits, so you can keep and + # reference separate object stores under the same registry + def sub_registry(prefix) + return BotRegistryAccessor.new(@bot, @orig_prefix + "+" + prefix) + 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) + Marshal.restore(val) + end + + # lookup a key in the registry + def [](key) + if @registry.has_key?(@prefix + key) + return restore(@registry[@prefix + key]) + elsif @default != nil + return restore(@default) + else + return nil + end + end + + # set a key in the registry + def []=(key,value) + @registry[@prefix + key] = 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 = store(default) + end + + # just like Hash#each + def each(&block) + @registry.each {|key,value| + if key.gsub!(/^#{Regexp.escape(@prefix)}/, "") + block.call(key, restore(value)) + end + } + end + + # just like Hash#each_key + def each_key(&block) + @registry.each {|key, value| + if key.gsub!(/^#{Regexp.escape(@prefix)}/, "") + block.call(key) + end + } + end + + # just like Hash#each_value + def each_value(&block) + @registry.each {|key, value| + if key =~ /^#{Regexp.escape(@prefix)}/ + block.call(restore(value)) + end + } + end + + # just like Hash#has_key? + def has_key?(key) + return @registry.has_key?(@prefix + key) + end + alias include? has_key? + alias member? has_key? + + # just like Hash#has_both? + def has_both?(key, value) + return @registry.has_both?(@prefix + key, 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) + ind = @registry.index(store(value)) + if ind.gsub!(/^#{Regexp.escape(@prefix)}/, "") + return ind + else + return nil + end + end + + # delete a key from the registry + def delete(key) + return @registry.delete(@prefix + key) + end + + # returns a list of your keys + def keys + return @registry.keys.collect {|key| + if key.gsub!(/^#{Regexp.escape(@prefix)}/, "") + key + else + nil + end + }.compact + end + + # Return an array of all associations [key, value] in your namespace + def to_a + ret = Array.new + @registry.each {|key, value| + if key.gsub!(/^#{Regexp.escape(@prefix)}/, "") + ret << [key, restore(value)] + end + } + return ret + end + + # Return an hash of all associations {key => value} in your namespace + def to_hash + ret = Hash.new + @registry.each {|key, value| + if key.gsub!(/^#{Regexp.escape(@prefix)}/, "") + ret[key] = restore(value) + end + } + return ret + end + + # empties the registry (restricted to your namespace) + def clear + @registry.each_key {|key| + if key =~ /^#{Regexp.escape(@prefix)}/ + @registry.delete(key) + end + } + 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| + if key =~ /^#{Regexp.escape(@prefix)}/ + Array << restore(v) + end + } + return ret + end + + # returns the number of keys in your registry namespace + def length + self.keys.length + end + alias size length + + def flush + @registry.flush + end + + end + +end |