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