]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/commitdiff
[registry] refactoring into a abstract and factory
authorMatthias H <apoc@sixserv.org>
Mon, 24 Feb 2014 03:45:28 +0000 (04:45 +0100)
committerMatthias H <apoc@sixserv.org>
Mon, 24 Feb 2014 03:45:28 +0000 (04:45 +0100)
* a new abstract class AbstractAccessor is the new base
  for all concrete database implementations.
* a factory now, dynamically discovers those implementations
  in the registry/ directory and will create the configured
  type for the plugins.
* again: this makes db keys case-sensitive (aka 'the correct
  way of doing things' -.-)
* re-added tokyocabinet

lib/rbot/ircbot.rb
lib/rbot/plugins.rb
lib/rbot/registry.rb [new file with mode: 0644]
lib/rbot/registry/daybreak.rb
lib/rbot/registry/dbm.rb
lib/rbot/registry/tc.rb [new file with mode: 0644]

index ea6a57c80be31fc6e0b5b0640111fbc61bea31e9..02414a0747dd5501ad0613feb094cbbe269796be 100644 (file)
@@ -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?
index a05a5b8f4d775145fabb239cec4116ae78bcd132..c499fd40ccef26c21ea2e24c4a966541b2c24dcf 100644 (file)
@@ -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 (file)
index 0000000..6c18df2
--- /dev/null
@@ -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
+
index 5d995379d30de5476f393d9296893e67b1aa70d8..6058b4086e0f65929472250c735c183a758af8e1 100644 (file)
@@ -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
index a0676539db26fea9102274a2429797a9cb3b6e92..a13cb8cef23ddd6a6d9b04a35b52cf9d98bc28f0 100644 (file)
@@ -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 (file)
index 0000000..63bfdf7
--- /dev/null
@@ -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
+