4 # :title: Registry: Persistent storage interface and factory
6 # This class provides persistent storage for plugins via a hash interface.
7 # The default mode is an object store, so you can store ruby objects and
8 # reference them with hash keys. This is because the default store/restore
9 # methods of the plugins' RegistryAccessor are calls to Marshal.dump and
14 # @registry[:blah] = blah
15 # then, even after the bot is shut down and disconnected, on the next run you
16 # can access the blah object as it was, with:
17 # blah = @registry[:blah]
18 # The registry can of course be used to store simple strings, fixnums, etc as
19 # well, and should be useful to store or cache plugin data or dynamic plugin
23 # in object store mode, don't make the mistake of treating it like a live
24 # object, e.g. (using the example above)
25 # @registry[:blah][:foo] = "flump"
26 # will NOT modify the object in the registry - remember that Registry#[]
27 # returns a Marshal.restore'd object, the object you just modified in place
28 # will disappear. You would need to:
29 # blah = @registry[:blah]
30 # blah[:foo] = "flump"
31 # @registry[:blah] = blah
33 # If you don't need to store objects, and strictly want a persistant hash of
34 # strings, you can override the store/restore methods to suit your needs, for
35 # example (in your plugin):
46 # Your plugins section of the registry is private, it has its own namespace
47 # (derived from the plugin's class name, so change it and lose your data).
48 # Calls to registry.each etc, will only iterate over your namespace.
55 # Dynamically loads the specified registry type library.
56 def initialize(format=nil)
57 @libpath = File.join(File.dirname(__FILE__), 'registry')
59 load File.join(@libpath, @format+'.rb') if format
60 # The get_impl method will return all implementations of the
61 # abstract accessor interface, since we only ever load one
62 # (the configured one) accessor implementation, we can just assume
63 # it to be the correct accessor to use.
64 accessors = AbstractAccessor.get_impl
65 if accessors.length > 1
66 warning 'multiple accessor implementations loaded!'
68 @accessor_class = accessors.first
71 # Returns a list of supported registry database formats.
73 Dir.glob(File.join(@libpath, '*.rb')).map do |name|
74 File.basename(name, File.extname(name))
78 # Creates a new Accessor object for the specified database filename.
79 def create(path, filename)
80 db = @accessor_class.new(File.join(path, 'registry_' + @format, filename.downcase))
85 # Helper method that will return a list of supported registry formats.
87 @@formats ||= Registry.new.discover
90 # Will detect tokyocabinet registry location: ~/.rbot/registry/*.tdb
91 # and move it to its new location ~/.rbot/registry_tc/*.tdb
92 def migrate_registry_folder(path)
93 old_name = File.join(path, 'registry')
94 new_name = File.join(path, 'registry_tc')
95 if @format == 'tc' and File.exists?(old_name) and
96 not File.exists?(new_name) and
97 not Dir.glob(File.join(old_name, '*.tdb')).empty?
98 File.rename(old_name, new_name)
102 # Abstract database accessor (a hash-like interface).
103 class AbstractAccessor
105 attr_reader :filename
107 # lets the user define a recovery procedure in case the Marshal
108 # deserialization fails, it might be manually recover data.
109 # NOTE: weird legacy stuff, used by markov plugin (WTH?)
110 attr_accessor :recovery
112 def initialize(filename)
113 debug "init registry accessor of #{self.class} for: #{filename}"
115 @name = File.basename filename
122 def sub_registry(prefix)
123 path = File.join(@filename.gsub(/\.[^\/\.]+$/,''), prefix.to_s)
124 @sub_registries[path] ||= self.class.new(path)
127 # creates the registry / subregistry folders
129 debug 'create folders for: ' + @filename
130 dirs = File.dirname(@filename).split("/")
131 dirs.length.times { |i|
132 dir = dirs[0,i+1].join("/")+"/"
133 unless File.exist?(dir)
139 # Will return true if the database file exists.
141 File.exists? @filename
144 # convert value to string form for storing in the registry
145 # defaults to Marshal.dump(val) but you can override this in your module's
146 # registry object to use any method you like.
147 # For example, if you always just handle strings use:
155 # restores object from string form, restore(store(val)) must return val.
156 # If you override store, you should override restore to reverse the
158 # For example, if you always just handle strings use:
165 rescue Exception => e
166 error _("failed to restore marshal data for #{val.inspect}, attempting recovery or fallback to default")
168 if defined? @recovery and @recovery
170 return @recovery.call(val)
171 rescue Exception => ee
172 error _("marshal recovery failed, trying default")
180 # Returned instead of nil if key wasnt found.
181 def set_default (default)
186 @default && (@default.dup rescue @default)
189 # Opens the database (if not already open) for read/write access.
191 create_folders unless dbexists?
194 # Forces flush/sync the database on disk.
196 return unless @registry
197 # if not supported by the database, close/reopen:
202 # Should optimize/vacuum the database. (if supported)
206 # Closes the database.
208 return unless @registry
213 # lookup a key in the registry
215 if dbexists? and registry.has_key?(key.to_s)
216 return restore(registry[key.to_s])
222 # set a key in the registry
224 registry[key.to_s] = store(value)
229 return nil unless dbexists?
230 registry.each do |key, value|
231 block.call(key, restore(value))
244 # like Hash#each_value
245 def each_value(&block)
246 self.each do |key, value|
251 # just like Hash#has_key?
253 return nil unless dbexists?
254 return registry.has_key?(key.to_s)
257 alias include? has_key?
258 alias member? has_key?
261 # just like Hash#has_value?
262 def has_value?(value)
263 return nil unless dbexists?
264 return registry.has_value?(store(value))
267 # just like Hash#index?
270 return k if v == value
275 # delete a key from the registry
276 # returns the value in success, nil otherwise
278 return default unless dbexists?
279 value = registry.delete(key.to_s)
285 # returns a list of your keys
287 return [] unless dbexists?
291 # Return an array of all associations [key, value] in your namespace
293 return [] unless dbexists?
295 self.each {|key, value|
301 # Return an hash of all associations {key => value} in your namespace
303 return {} unless dbexists?
305 self.each {|key, value|
311 # empties the registry (restricted to your namespace)
313 return unless dbexists?
318 # returns an array of the values in your namespace of the registry
320 return [] unless dbexists?
328 # returns the number of keys in your registry namespace
330 return 0 unless dbexists?
335 # Returns all classes from the namespace that implement this interface
337 ObjectSpace.each_object(Class).select { |klass| klass.ancestors[1] == self }