]> git.netwichtig.de Git - user/henk/code/ruby/rbot.git/blob - lib/rbot/registry/dbm.rb
a0676539db26fea9102274a2429797a9cb3b6e92
[user/henk/code/ruby/rbot.git] / lib / rbot / registry / dbm.rb
1 #-- vim:sw=2:et
2 #++
3 #
4 # The DBM class of the ruby std-lib provides wrappers for Unix-style
5 # dbm or Database Manager libraries. The exact library used depends
6 # on how ruby was compiled. Its any of the following: ndbm, bdb,
7 # gdbm or qdbm.
8 # DBM API Documentation:
9 # http://ruby-doc.org/stdlib-2.1.0/libdoc/dbm/rdoc/DBM.html
10 #
11 # :title: DB interface
12
13 require 'dbm'
14
15 module Irc
16 class Bot
17 class Registry
18
19   # This class provides persistent storage for plugins via a hash interface.
20   # The default mode is an object store, so you can store ruby objects and
21   # reference them with hash keys. This is because the default store/restore
22   # methods of the plugins' RegistryAccessor are calls to Marshal.dump and
23   # Marshal.restore,
24   # for example:
25   #   blah = Hash.new
26   #   blah[:foo] = "fum"
27   #   @registry[:blah] = blah
28   # then, even after the bot is shut down and disconnected, on the next run you
29   # can access the blah object as it was, with:
30   #   blah = @registry[:blah]
31   # The registry can of course be used to store simple strings, fixnums, etc as
32   # well, and should be useful to store or cache plugin data or dynamic plugin
33   # configuration.
34   #
35   # WARNING:
36   # in object store mode, don't make the mistake of treating it like a live
37   # object, e.g. (using the example above)
38   #   @registry[:blah][:foo] = "flump"
39   # will NOT modify the object in the registry - remember that Registry#[]
40   # returns a Marshal.restore'd object, the object you just modified in place
41   # will disappear. You would need to:
42   #   blah = @registry[:blah]
43   #   blah[:foo] = "flump"
44   #   @registry[:blah] = blah
45   #
46   # If you don't need to store objects, and strictly want a persistant hash of
47   # strings, you can override the store/restore methods to suit your needs, for
48   # example (in your plugin):
49   #   def initialize
50   #     class << @registry
51   #       def store(val)
52   #         val
53   #       end
54   #       def restore(val)
55   #         val
56   #       end
57   #     end
58   #   end
59   # Your plugins section of the registry is private, it has its own namespace
60   # (derived from the plugin's class name, so change it and lose your data).
61   # Calls to registry.each etc, will only iterate over your namespace.
62   class Accessor
63
64     attr_accessor :recovery
65
66     # plugins don't call this - a Registry::Accessor is created for them and
67     # is accessible via @registry.
68     def initialize(bot, name)
69       @bot = bot
70       @name = name.downcase
71       @filename = @bot.path 'registry_dbm', @name
72       dirs = File.dirname(@filename).split("/")
73       dirs.length.times { |i|
74         dir = dirs[0,i+1].join("/")+"/"
75         unless File.exist?(dir)
76           debug "creating subregistry directory #{dir}"
77           Dir.mkdir(dir)
78         end
79       }
80       @registry = nil
81       @default = nil
82       @recovery = nil
83       # debug "initializing registry accessor with name #{@name}"
84     end
85
86     def registry
87       @registry ||= DBM.open(@filename, 0666, DBM::WRCREAT)
88     end
89
90     def flush
91       return if !@registry
92       # ruby dbm has no flush, so we close/reopen :(
93       close
94       registry
95     end
96
97     def close
98       return if !@registry
99       registry.close
100       @registry = nil
101     end
102
103     # convert value to string form for storing in the registry
104     # defaults to Marshal.dump(val) but you can override this in your module's
105     # registry object to use any method you like.
106     # For example, if you always just handle strings use:
107     #   def store(val)
108     #     val
109     #   end
110     def store(val)
111       Marshal.dump(val)
112     end
113
114     # restores object from string form, restore(store(val)) must return val.
115     # If you override store, you should override restore to reverse the
116     # action.
117     # For example, if you always just handle strings use:
118     #   def restore(val)
119     #     val
120     #   end
121     def restore(val)
122       begin
123         Marshal.restore(val)
124       rescue Exception => e
125         error _("failed to restore marshal data for #{val.inspect}, attempting recovery or fallback to default")
126         debug e
127         if defined? @recovery and @recovery
128           begin
129             return @recovery.call(val)
130           rescue Exception => ee
131             error _("marshal recovery failed, trying default")
132             debug ee
133           end
134         end
135         return default
136       end
137     end
138
139     # lookup a key in the registry
140     def [](key)
141       if registry.has_key?(key.to_s)
142         return restore(registry[key.to_s])
143       else
144         return default
145       end
146     end
147
148     # set a key in the registry
149     def []=(key,value)
150       registry[key.to_s] = store(value)
151     end
152
153     # set the default value for registry lookups, if the key sought is not
154     # found, the default will be returned. The default default (har) is nil.
155     def set_default (default)
156       @default = default
157     end
158
159     def default
160       @default && (@default.dup rescue @default)
161     end
162
163     # like Hash#each
164     def each(&block)
165       registry.each_key do |key|
166         block.call(key, self[key])
167       end
168     end
169
170     alias each_pair each
171
172     # like Hash#each_key
173     def each_key(&block)
174       registry.each_key do |key|
175         block.call(key)
176       end
177     end
178
179     # like Hash#each_value
180     def each_value(&block)
181       registry.each_key do |key|
182         block.call(self[key])
183       end
184     end
185
186     # just like Hash#has_key?
187     def has_key?(key)
188       return registry.has_key?(key.to_s)
189     end
190
191     alias include? has_key?
192     alias member? has_key?
193     alias key? has_key?
194
195     # just like Hash#has_both?
196     def has_both?(key, value)
197       registry.has_key?(key.to_s) and registry.has_value?(store(value))
198     end
199
200     # just like Hash#has_value?
201     def has_value?(value)
202       return registry.has_value?(store(value))
203     end
204
205     # just like Hash#index?
206     def index(value)
207       self.each do |k,v|
208         return k if v == value
209       end
210       return nil
211     end
212
213     # delete a key from the registry
214     def delete(key)
215       return registry.delete(key.to_s)
216     end
217
218     # returns a list of your keys
219     def keys
220       return registry.keys
221     end
222
223     # Return an array of all associations [key, value] in your namespace
224     def to_a
225       ret = Array.new
226       registry.each {|key, value|
227         ret << [key, restore(value)]
228       }
229       return ret
230     end
231
232     # Return an hash of all associations {key => value} in your namespace
233     def to_hash
234       ret = Hash.new
235       registry.each {|key, value|
236         ret[key] = restore(value)
237       }
238       return ret
239     end
240
241     # empties the registry (restricted to your namespace)
242     def clear
243       registry.clear
244     end
245     alias truncate clear
246
247     # returns an array of the values in your namespace of the registry
248     def values
249       ret = Array.new
250       self.each {|k,v|
251         ret << restore(v)
252       }
253       return ret
254     end
255
256     def sub_registry(prefix)
257       return Accessor.new(@bot, @name + "/" + prefix.to_s)
258     end
259
260     # returns the number of keys in your registry namespace
261     def length
262       registry.length
263     end
264     alias size length
265   end
266
267 end # Registry
268 end # Bot
269 end # Irc
270