initial import of rbot
[rbot] / rbot / registry.rb
1 # Copyright (C) 2002 Tom Gilbert.
2 #
3 # Permission is hereby granted, free of charge, to any person obtaining a copy
4 # of this software and associated documentation files (the "Software"), to
5 # deal in the Software without restriction, including without limitation the
6 # rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
7 # sell copies of the Software, and to permit persons to whom the Software is
8 # furnished to do so, subject to the following conditions:
9 #
10 # The above copyright notice and this permission notice shall be included in
11 # all copies of the Software and its documentation and acknowledgment shall be
12 # given in the documentation and software packages that this Software was
13 # used.
14 #
15 # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
18 # THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
19 # IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
20 # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21   
22 require 'rbot/dbhash'
23
24 module Irc
25
26   # this is the backend of the RegistryAccessor class, which ties it to a
27   # DBHash object called plugin_registry(.db). All methods are delegated to
28   # the DBHash.
29   class BotRegistry
30     def initialize(bot)
31       @bot = bot
32       upgrade_data
33       @db = DBTree.new @bot, "plugin_registry"
34     end
35
36     # delegation hack
37     def method_missing(method, *args, &block)
38       @db.send(method, *args, &block)
39     end
40
41     # check for older versions of rbot with data formats that require updating
42     # NB this function is called _early_ in init(), pretty much all you have to
43     # work with is @bot.botclass.
44     def upgrade_data
45       if File.exist?("#{@bot.botclass}/registry.db")
46         puts "upgrading old-style (rbot 0.9.5 or earlier) plugin registry to new format"
47         old = BDB::Hash.open "#{@bot.botclass}/registry.db", nil, 
48                              "r+", 0600, "set_pagesize" => 1024,
49                              "set_cachesize" => [0, 32 * 1024, 0]
50         new = BDB::CIBtree.open "#{@bot.botclass}/plugin_registry.db", nil, 
51                                 BDB::CREATE | BDB::EXCL | BDB::TRUNCATE,
52                                 0600, "set_pagesize" => 1024,
53                                 "set_cachesize" => [0, 32 * 1024, 0]
54         old.each {|k,v|
55           new[k] = v
56         }
57         old.close
58         new.close
59         File.delete("#{@bot.botclass}/registry.db")
60       end
61     end
62   end
63
64   # This class provides persistent storage for plugins via a hash interface.
65   # The default mode is an object store, so you can store ruby objects and
66   # reference them with hash keys. This is because the default store/restore
67   # methods of the plugins' RegistryAccessor are calls to Marshal.dump and
68   # Marshal.restore,
69   # for example:
70   #   blah = Hash.new
71   #   blah[:foo] = "fum"
72   #   @registry[:blah] = blah
73   # then, even after the bot is shut down and disconnected, on the next run you
74   # can access the blah object as it was, with:
75   #   blah = @registry[:blah]
76   # The registry can of course be used to store simple strings, fixnums, etc as
77   # well, and should be useful to store or cache plugin data or dynamic plugin
78   # configuration. 
79   #
80   # WARNING:
81   # in object store mode, don't make the mistake of treating it like a live
82   # object, e.g. (using the example above)
83   #   @registry[:blah][:foo] = "flump"
84   # will NOT modify the object in the registry - remember that BotRegistry#[]
85   # returns a Marshal.restore'd object, the object you just modified in place
86   # will disappear. You would need to:
87   #   blah = @registry[:blah]
88   #   blah[:foo] = "flump"
89   #   @registry[:blah] = blah
90
91   # If you don't need to store objects, and strictly want a persistant hash of
92   # strings, you can override the store/restore methods to suit your needs, for
93   # example (in your plugin):
94   #   def initialize
95   #     class << @registry
96   #       def store(val)
97   #         val
98   #       end
99   #       def restore(val)
100   #         val
101   #       end
102   #     end
103   #   end
104   # Your plugins section of the registry is private, it has its own namespace
105   # (derived from the plugin's class name, so change it and lose your data).
106   # Calls to registry.each etc, will only iterate over your namespace.
107   class BotRegistryAccessor
108     # plugins don't call this - a BotRegistryAccessor is created for them and
109     # is accessible via @registry.
110     def initialize(bot, prefix)
111       @bot = bot
112       @registry = @bot.registry
113       @orig_prefix = prefix
114       @prefix = prefix + "/"
115       @default = nil
116       # debug "initializing registry accessor with prefix #{@prefix}"
117     end
118
119     # use this to chop up your namespace into bits, so you can keep and
120     # reference separate object stores under the same registry
121     def sub_registry(prefix)
122       return BotRegistryAccessor.new(@bot, @orig_prefix + "+" + prefix)
123     end
124
125     # convert value to string form for storing in the registry
126     # defaults to Marshal.dump(val) but you can override this in your module's
127     # registry object to use any method you like.
128     # For example, if you always just handle strings use:
129     #   def store(val)
130     #     val
131     #   end
132     def store(val)
133       Marshal.dump(val)
134     end
135
136     # restores object from string form, restore(store(val)) must return val.
137     # If you override store, you should override restore to reverse the
138     # action.
139     # For example, if you always just handle strings use:
140     #   def restore(val)
141     #     val
142     #   end
143     def restore(val)
144       Marshal.restore(val)
145     end
146
147     # lookup a key in the registry
148     def [](key)
149       if @registry.has_key?(@prefix + key)
150         return restore(@registry[@prefix + key])
151       elsif @default != nil
152         return restore(@default)
153       else
154         return nil
155       end
156     end
157
158     # set a key in the registry
159     def []=(key,value)
160       @registry[@prefix + key] = store(value)
161     end
162
163     # set the default value for registry lookups, if the key sought is not
164     # found, the default will be returned. The default default (har) is nil.
165     def set_default (default)
166       @default = store(default)
167     end
168
169     # just like Hash#each
170     def each(&block)
171       @registry.each {|key,value|
172         if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
173           block.call(key, restore(value))
174         end
175       }
176     end
177     
178     # just like Hash#each_key
179     def each_key(&block)
180       @registry.each {|key, value|
181         if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
182           block.call(key)
183         end
184       }
185     end
186     
187     # just like Hash#each_value
188     def each_value(&block)
189       @registry.each {|key, value|
190         if key =~ /^#{Regexp.escape(@prefix)}/
191           block.call(restore(value))
192         end
193       }
194     end
195
196     # just like Hash#has_key?
197     def has_key?(key)
198       return @registry.has_key?(@prefix + key)
199     end
200     alias include? has_key?
201     alias member? has_key?
202
203     # just like Hash#has_both?
204     def has_both?(key, value)
205       return @registry.has_both?(@prefix + key, store(value))
206     end
207     
208     # just like Hash#has_value?
209     def has_value?(value)
210       return @registry.has_value(store(value))
211     end
212
213     # just like Hash#index?
214     def index(value)
215       ind = @registry.index(store(value))
216       if ind.gsub!(/^#{Regexp.escape(@prefix)}/, "")
217         return ind
218       else
219         return nil
220       end
221     end
222     
223     # delete a key from the registry
224     def delete(key)
225       return @registry.delete(@prefix + key)
226     end
227
228     # returns a list of your keys
229     def keys
230       return @registry.keys.collect {|key|
231           if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")  
232             key
233           else
234             nil
235           end
236         }.compact
237     end
238
239     # Return an array of all associations [key, value] in your namespace
240     def to_a
241       ret = Array.new
242       @registry.each {|key, value|
243         if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
244           ret << [key, restore(value)]
245         end
246       }
247       return ret
248     end
249     
250     # Return an hash of all associations {key => value} in your namespace
251     def to_hash
252       ret = Hash.new
253       @registry.each {|key, value|
254         if key.gsub!(/^#{Regexp.escape(@prefix)}/, "")
255           ret[key] = restore(value)
256         end
257       }
258       return ret
259     end
260
261     # empties the registry (restricted to your namespace)
262     def clear
263       @registry.each_key {|key|
264         if key =~ /^#{Regexp.escape(@prefix)}/
265           @registry.delete(key)
266         end
267       }
268     end
269     alias truncate clear
270
271     # returns an array of the values in your namespace of the registry
272     def values
273       ret = Array.new
274       self.each {|k,v|
275         if key =~ /^#{Regexp.escape(@prefix)}/
276           Array << restore(v)
277         end
278       }
279       return ret
280     end
281
282     # returns the number of keys in your registry namespace
283     def length
284       self.keys.length
285     end
286     alias size length
287
288     def flush
289       @registry.flush
290     end
291     
292   end
293
294 end