8 if BDB::VERSION_MAJOR < 4
9 fatal "Your bdb (Berkeley DB) version #{BDB::VERSION} is too old!"
10 fatal "rbot will only run with bdb version 4 or higher, please upgrade."
11 fatal "For maximum reliability, upgrade to version 4.2 or higher."
12 raise BDB::Fatal, BDB::VERSION + " is too old"
15 if BDB::VERSION_MAJOR == 4 and BDB::VERSION_MINOR < 2
16 warning "Your bdb (Berkeley DB) version #{BDB::VERSION} may not be reliable."
17 warning "If possible, try upgrade version 4.2 or later."
20 warning "rbot couldn't load the bdb module. Old registries won't be upgraded"
22 warning "rbot couldn't load the bdb module: #{e.pretty_inspect}"
28 require 'tokyocabinet'
33 # DBHash is for tying a hash to disk (using bdb).
34 # Call it with an identifier, for example "mydata". It'll look for
35 # mydata.db, if it exists, it will load and reference that db.
36 # Otherwise it'll create and empty db called mydata.db
39 # absfilename:: use +key+ as an actual filename, don't prepend the bot's
40 # config path and don't append ".db"
41 def initialize(bot, key, absfilename=false)
44 relfilename = @bot.path key
46 if absfilename && File.exist?(key)
47 # db already exists, use it
48 @db = DBHash.open_db(key)
51 @db = DBHash.create_db(key)
52 elsif File.exist? relfilename
53 # db already exists, use it
54 @db = DBHash.open_db relfilename
57 @db = DBHash.create_db relfilename
61 def method_missing(method, *args, &block)
62 return @db.send(method, *args, &block)
65 def DBHash.create_db(name)
66 debug "DBHash: creating empty db #{name}"
67 return BDB::Hash.open(name, nil,
68 BDB::CREATE | BDB::EXCL, 0600)
71 def DBHash.open_db(name)
72 debug "DBHash: opening existing db #{name}"
73 return BDB::Hash.open(name, nil, "r+", 0600)
77 # make BTree lookups case insensitive
80 def bdb_bt_compare(a, b)
81 if a == nil || b == nil
82 warning "CIBTree: comparing #{a.inspect} (#{self[a].inspect}) with #{b.inspect} (#{self[b].inspect})"
84 (a||'').downcase <=> (b||'').downcase
91 class CIBDB < TokyoCabinet::BDB
95 self.setcmpfunc(Proc.new do |a, b|
96 a.downcase <=> b.downcase
104 # DBTree is a BTree equivalent of DBHash, with case insensitive lookups.
106 # absfilename:: use +key+ as an actual filename, don't prepend the bot's
107 # config path and don't append ".db"
108 def initialize(bot, key, absfilename=false)
112 relfilename = @bot.path key
113 relfilename << '.tdb'
115 if absfilename && File.exist?(key)
116 # db already exists, use it
117 @db = DBTree.open_db(key)
120 @db = DBTree.create_db(key)
121 elsif File.exist? relfilename
122 # db already exists, use it
123 @db = DBTree.open_db relfilename
126 @db = DBTree.create_db relfilename
128 oldbasename = (absfilename ? key : relfilename).gsub(/\.tdb$/, ".db")
129 if File.exists? oldbasename and defined? BDB
131 warning "Upgrading old database #{oldbasename}..."
132 oldb = ::BDB::Btree.open(oldbasename, nil, "r", 0600)
135 @db.putlist k, (oldb.duplicates(k, false))
138 File.rename oldbasename, oldbasename+".bak"
143 def method_missing(method, *args, &block)
144 return @db.send(method, *args, &block)
147 def DBTree.create_db(name)
148 debug "DBTree: creating empty db #{name}"
149 db = TokyoCabinet::CIBDB.new
150 res = db.open(name, TokyoCabinet::CIBDB::OREADER | TokyoCabinet::CIBDB::OCREAT | TokyoCabinet::CIBDB::OWRITER)
151 warning "DBTree: creating empty db #{name}: #{db.errmsg(db.ecode) unless res}"
155 def DBTree.open_db(name)
156 debug "DBTree: opening existing db #{name}"
157 db = TokyoCabinet::CIBDB.new
158 res = db.open(name, TokyoCabinet::CIBDB::OREADER | TokyoCabinet::CIBDB::OWRITER)
159 warning "DBTree:opening db #{name}: #{db.errmsg(db.ecode) unless res}"
163 def DBTree.cleanup_logs()
171 def DBTree.cleanup_env()
182 # This class is now used purely for upgrading from prior versions of rbot
183 # the new registry is split into multiple DBHash objects, one per plugin
191 # check for older versions of rbot with data formats that require updating
192 # NB this function is called _early_ in init(), pretty much all you have to
193 # work with is @bot.botclass.
195 oldreg = @bot.path 'registry.db'
197 newreg = @bot.path 'plugin_registry.db'
198 if File.exist?(oldreg)
199 log _("upgrading old-style (rbot 0.9.5 or earlier) plugin registry to new format")
200 old = ::BDB::Hash.open(oldreg, nil, "r+", 0600)
201 new = ::BDB::CIBtree.open(newreg, nil, ::BDB::CREATE | ::BDB::EXCL, 0600)
207 File.rename(oldreg, oldreg + ".old")
210 warning "Won't upgrade data: BDB not installed" if File.exist? oldreg
215 oldreg = @bot.path 'plugin_registry.db'
217 warning "Won't upgrade data: BDB not installed" if File.exist? oldreg
220 newdir = @bot.path 'registry'
221 if File.exist?(oldreg)
222 Dir.mkdir(newdir) unless File.exist?(newdir)
223 env = BDB::Env.open(@bot.botclass, BDB::INIT_TRANSACTION | BDB::CREATE | BDB::RECOVER)# | BDB::TXN_NOSYNC)
225 log _("upgrading previous (rbot 0.9.9 or earlier) plugin registry to new split format")
226 old = BDB::CIBtree.open(oldreg, nil, "r+", 0600, "env" => env)
228 prefix,key = k.split("/", 2)
230 # subregistries were split with a +, now they are in separate folders
231 if prefix.gsub!(/\+/, "/")
232 # Ok, this code needs to be put in the db opening routines
233 dirs = File.dirname("#{@bot.botclass}/registry/#{prefix}.db").split("/")
234 dirs.length.times { |i|
235 dir = dirs[0,i+1].join("/")+"/"
236 unless File.exist?(dir)
237 log _("creating subregistry directory #{dir}")
242 unless dbs.has_key?(prefix)
243 log _("creating db #{@bot.botclass}/registry/#{prefix}.tdb")
244 dbs[prefix] = TokyoCabinet::CIBDB.open("#{@bot.botclass}/registry/#{prefix}.tdb",
245 TokyoCabinet::CIBDB::OREADER | TokyoCabinet::CIBDB::OCREAT | TokyoCabinet::CIBDB::OWRITER)
250 File.rename(oldreg, oldreg + ".old")
252 log _("closing db #{k}")
259 # This class provides persistent storage for plugins via a hash interface.
260 # The default mode is an object store, so you can store ruby objects and
261 # reference them with hash keys. This is because the default store/restore
262 # methods of the plugins' RegistryAccessor are calls to Marshal.dump and
267 # @registry[:blah] = blah
268 # then, even after the bot is shut down and disconnected, on the next run you
269 # can access the blah object as it was, with:
270 # blah = @registry[:blah]
271 # The registry can of course be used to store simple strings, fixnums, etc as
272 # well, and should be useful to store or cache plugin data or dynamic plugin
276 # in object store mode, don't make the mistake of treating it like a live
277 # object, e.g. (using the example above)
278 # @registry[:blah][:foo] = "flump"
279 # will NOT modify the object in the registry - remember that Registry#[]
280 # returns a Marshal.restore'd object, the object you just modified in place
281 # will disappear. You would need to:
282 # blah = @registry[:blah]
283 # blah[:foo] = "flump"
284 # @registry[:blah] = blah
286 # If you don't need to store objects, and strictly want a persistant hash of
287 # strings, you can override the store/restore methods to suit your needs, for
288 # example (in your plugin):
299 # Your plugins section of the registry is private, it has its own namespace
300 # (derived from the plugin's class name, so change it and lose your data).
301 # Calls to registry.each etc, will only iterate over your namespace.
304 attr_accessor :recovery
306 # plugins don't call this - a Registry::Accessor is created for them and
307 # is accessible via @registry.
308 def initialize(bot, name)
310 @name = name.downcase
311 @filename = @bot.path 'registry', @name
312 dirs = File.dirname(@filename).split("/")
313 dirs.length.times { |i|
314 dir = dirs[0,i+1].join("/")+"/"
315 unless File.exist?(dir)
316 debug "creating subregistry directory #{dir}"
324 # debug "initializing registry accessor with name #{@name}"
328 @registry ||= DBTree.new @bot, "registry/#{@name}"
332 # debug "fushing registry #{registry}"
338 # debug "closing registry #{registry}"
343 # convert value to string form for storing in the registry
344 # defaults to Marshal.dump(val) but you can override this in your module's
345 # registry object to use any method you like.
346 # For example, if you always just handle strings use:
354 # restores object from string form, restore(store(val)) must return val.
355 # If you override store, you should override restore to reverse the
357 # For example, if you always just handle strings use:
364 rescue Exception => e
365 error _("failed to restore marshal data for #{val.inspect}, attempting recovery or fallback to default")
367 if defined? @recovery and @recovery
369 return @recovery.call(val)
370 rescue Exception => ee
371 error _("marshal recovery failed, trying default")
379 # lookup a key in the registry
381 if File.exist?(@filename) and registry.has_key?(key.to_s)
382 return restore(registry[key.to_s])
388 # set a key in the registry
390 registry[key.to_s] = store(value)
393 # set the default value for registry lookups, if the key sought is not
394 # found, the default will be returned. The default default (har) is nil.
395 def set_default (default)
400 @default && (@default.dup rescue @default)
403 # just like Hash#each
404 def each(set=nil, bulk=0, &block)
405 return nil unless File.exist?(@filename)
406 registry.fwmkeys(set).each {|key|
407 block.call(key, restore(registry[key]))
411 # just like Hash#each_key
412 def each_key(set=nil, bulk=0, &block)
413 return nil unless File.exist?(@filename)
414 registry.fwmkeys(set).each do |key|
419 # just like Hash#each_value
420 def each_value(set=nil, bulk=0, &block)
421 return nil unless File.exist?(@filename)
422 registry.fwmkeys(set).each do |key|
423 block.call(restore(registry[key]))
427 # just like Hash#has_key?
429 return false unless File.exist?(@filename)
430 return registry.has_key?(key.to_s)
433 alias include? has_key?
434 alias member? has_key?
437 # just like Hash#has_both?
438 def has_both?(key, value)
439 return false unless File.exist?(@filename)
440 registry.has_key?(key.to_s) and registry.has_value?(store(value))
443 # just like Hash#has_value?
444 def has_value?(value)
445 return false unless File.exist?(@filename)
446 return registry.has_value?(store(value))
449 # just like Hash#index?
452 return k if v == value
457 # delete a key from the registry
459 return default unless File.exist?(@filename)
460 return registry.delete(key.to_s)
463 # returns a list of your keys
465 return [] unless File.exist?(@filename)
469 # Return an array of all associations [key, value] in your namespace
471 return [] unless File.exist?(@filename)
473 registry.each {|key, value|
474 ret << [key, restore(value)]
479 # Return an hash of all associations {key => value} in your namespace
481 return {} unless File.exist?(@filename)
483 registry.each {|key, value|
484 ret[key] = restore(value)
489 # empties the registry (restricted to your namespace)
491 return true unless File.exist?(@filename)
496 # returns an array of the values in your namespace of the registry
498 return [] unless File.exist?(@filename)
506 def sub_registry(prefix)
507 return Accessor.new(@bot, @name + "/" + prefix.to_s)
510 # returns the number of keys in your registry namespace
512 return 0 unless File.exist?(@filename)
518 def putdup(key, value)
519 registry.putdup(key.to_s, store(value))
522 def putlist(key, values)
523 registry.putlist(key.to_s, value.map {|v| store(v)})
527 return [] unless File.exist?(@filename)
528 (registry.getlist(key.to_s) || []).map {|v| restore(v)}