summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/rbot/ircbot.rb17
-rw-r--r--lib/rbot/plugins.rb2
-rw-r--r--lib/rbot/registry.rb326
-rw-r--r--lib/rbot/registry/daybreak.rb245
-rw-r--r--lib/rbot/registry/dbm.rb244
-rw-r--r--lib/rbot/registry/tc.rb44
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
+