1
# gozerbot/persistconfig.py
5
""" allow data to be pickled to disk .. creating the persisted object
9
!plug-cfg -> shows list of all config
10
!plug-cfg key value -> sets value to key
11
!plug-cfg key -> shows list of key
12
!plug-cfg key add value -> adds value to list
13
!plug-cfg key remove value -> removes value from list
14
!plug-cfg key clear -> clears entire list
15
!plug-cfgsave -> force save configuration to disk
18
- plugin api (more work needed?)
22
__copyright__ = 'this file is in the public domain'
23
__author__ = 'Bas van Oostveen'
25
from gozerbot.utils.log import rlog
26
from gozerbot.utils.trace import calledfrom
27
from gozerbot.compat.persist import Persist
28
from gozerbot.commands import cmnds, Command
29
from gozerbot.examples import examples
30
from gozerbot.datadir import datadir
31
import sys, os, types, time
35
def __init__(self, value, desc, perm, example, name, exposed):
36
assert isinstance(name, str), "Option.self.name must be a string"
40
self.example = example
41
self.name = name.lower()
42
self.exposed = exposed
44
def __casattr__(self, name, val):
45
# Update option if needed
46
if not hasattr(self, name):
47
setattr(self, name, val)
50
#if val and getattr(self, name)!=val: # old style
51
if val != None and type(getattr(self, name)) != type(val):
52
setattr(self, name, val)
56
def check(self, key, plugname, value, desc, perm, example, name, exposed):
58
# maybe checking value is too much here
59
if self.__casattr__("value", value): upd = True
60
if self.__casattr__("example", example): upd = True
61
if self.__casattr__("name", name): upd = True
62
if self.__casattr__("perm", perm): upd = True
63
if self.__casattr__("exposed", exposed): upd = True
65
self.name = "%s-cfg-%s" % (plugname, str(key))
69
def __lt__(self, other):
70
return self.value < other
72
def __le__(self, other):
73
return self.value <= other
75
def __eq__(self, other):
76
return self.value == other
78
def __ne__(self, other):
79
return self.value != other
81
def __gt__(self, other):
82
return self.value >= other
84
def __ge__(self, other):
85
return self.value >= other
87
class LazyValueDict(object):
89
""" emulates the normal Persist.data (key, value) dict """
91
def __init__(self, cfg):
95
return len(self.__persistData)
97
def __getitem__(self, key):
98
return self.__cfg.data[key].value
100
def __setitem__(self, key, value):
101
if not self.__cfg.data.has_key(key) or not \
102
isinstance(self.__cfg.data[key], Option):
103
name = "%s-cfg-%s" % (self.__cfg.plugname, str(key))
104
self.__cfg.define(value, "", 'OPER', "", name, exposed=False)
105
self.__cfg.set(key, value)
107
def __delitem__(self, key):
108
raise Exception("Direct deletion not supported, use \
109
persistConfig.undefine()")
112
return self.__cfg.data.__iter__()
115
return self.__iter__()
117
def __contains__(self, item):
118
return self.__cfg.data.has_key(item)
120
class PersistConfigError(Exception): pass
122
class PersistConfig(Persist):
124
""" persist plugin configuration and create default handlers """
127
self.__basename__ = self.__class__.__name__
128
self.plugname = calledfrom(sys._getframe())
129
Persist.__init__(self, os.path.join(datadir, "%s-config" % \
131
self.__callbacks = {}
132
cmndname = "%s-cfg" % self.plugname
133
rlog(-3, 'persistconfig', 'added command %s (%s)' % (cmndname, \
135
cmnds[cmndname] = Command(self.cmnd_cfg, 'OPER', self.plugname, \
137
examples.add(cmndname, "plugin configuration", cmndname)
138
cmndnamesave = cmndname + "save"
139
cmnds[cmndnamesave] = Command(self.cmnd_cfgsave, 'OPER', \
140
self.plugname, threaded=True)
141
examples.add(cmndnamesave, "save plugin configuration", cmndnamesave)
143
def __getattribute__(self, name):
144
# make sure the attribute data is not called from Persist or
145
# PersistConfig returning a persist compatible (key, value) dict
146
# instead of the rich persistconfig
147
cf = calledfrom(sys._getframe())
148
## (key, option(value, ...)) is only for persistconfig internal usage.
149
if name == "data" and cf != "persistconfig" and cf != "persist" and \
150
cf != self.__basename__:
151
# intercept data block, return a clean dict with lazy binding
153
return LazyValueDict(self)
154
return super(PersistConfig, self).__getattribute__(name)
156
def handle_callback(self, event, key, value=None):
157
if self.__callbacks.has_key((key, event)):
158
cb, extra_data = self.__callbacks[(key, event)]
160
cb(key, value, event, extra_data)
162
rlog(5, 'persistconfig', 'invalid callback for %s %s' % (key, \
164
del self.__callbacks[(key, event)]
168
def show_cfg(self, bot, ievent):
170
for key, option in sorted(self.data.items()):
171
if not isinstance(option, Option):
172
rlog(5, 'persistconfig', 'Option %s is not a valid option' % \
175
if not option.exposed:
178
if type(v) in [str, unicode]:
181
s.append("%s=%s" % (key, v))
182
ievent.reply("options: " + ' .. '.join(s))
184
def cmnd_cfgsave(self, bot, ievent):
186
ievent.reply("config saved")
188
def cmnd_cfg_edit(self, bot, ievent, args, key, option):
189
if type(option.value) == types.ListType:
190
if args[0].startswith("[") and args[-1].endswith("]"):
192
for v in ' '.join(args)[1:-1].replace(", ", ",").split(","):
193
if v[0]=='"' and v[-1]=='"':
195
v = v.replace('"', '')
196
elif v[0]=="'" and v[-1]=="'":
198
v = v.replace("'", "")
204
ievent.reply("invalid long literal: %s" % v)
211
ievent.reply("invalid int literal: %s" % v)
214
self.set(key, values)
215
ievent.reply("%s set %s" % (key, values))
218
value = ' '.join(args[1:])
219
if command == "clear":
221
ievent.reply("list empty")
222
elif command == "add":
223
self.append(key, value)
224
ievent.reply("%s added %s" % (key, value))
225
elif command == "remove" or command == "del":
227
self.remove(key, value)
228
ievent.reply("%s removed" % str(value))
230
ievent.reply("%s is not in list" % str(value))
232
ievent.reply("invalid command")
235
value = ' '.join(args)
237
value = type(option.value)(value)
240
if type(value) == type(option.value):
242
ievent.reply("%s set" % key)
243
elif type(value) == types.LongType and \
244
type(option.value) == types.IntType:
245
# allow upscaling from int to long
247
ievent.reply("%s set" % key)
249
ievent.reply("value %s (%s) is not of the same type as %s \
250
(%s)" % (value, type(value), option.value, type(option.value)))
252
def cmnd_cfg(self, bot, ievent):
254
self.show_cfg(bot, ievent)
256
argc = len(ievent.args)
259
option = self.data[key]
261
ievent.reply("%s option %s not found" % (self.plugname, key))
263
if not isinstance(option, Option):
264
rlog(5, 'persistconfig', 'Option %s is not a valid option' % key)
266
if not option.exposed:
269
ievent.reply(str(option.value))
271
self.cmnd_cfg_edit(bot, ievent, ievent.args[1:], key, option)
273
def generic_cmnd(self, key):
274
def func(bot, ievent):
276
option = self.data[key]
278
ievent.reply("%s not found" % key)
280
if not isinstance(option, Option):
281
rlog(5, 'persistconfig', 'Option %s is not a valid option' % \
285
value = ' '.join(ievent.args)
287
value = type(option.value)(value)
290
self.cmnd_cfg_edit(bot, ievent, ievent.args, key, option)
292
ievent.reply(str(option.value))
297
def define(self, key, value=None, desc="plugin option", perm='OPER', \
298
example="", name=None, exposed=True):
301
if not self.data.has_key(key):
303
name = "%s-cfg-%s" % (self.plugname, str(key))
304
option = Option(value, desc, perm, example, name, exposed)
305
self.data[key] = option
308
option = self.data[key]
309
# if unpickled option is not of class Option recreate the option
310
# also if the type of value has changed recreate the option
311
# exception if value got upgraded from int to long, then nothing
313
if not isinstance(option, Option):
315
name = "%s-cfg-%s" % (self.plugname, str(key))
316
if type(value) == type(option):
318
option = Option(value, desc, perm, example, name, exposed)
319
self.data[key] = option
322
if type(option.value) == types.LongType and \
323
type(value) == types.IntType:
325
if option.check(key, self.plugname, value, desc, perm, \
326
example, name, exposed):
327
self.data[key] = option
330
def undefine(self, key, throw=False):
332
option = self.data[key]
334
if cmnds.has_key(option.name):
335
del cmnds[option.name]
336
if examples.has_key(option.name):
337
del examples[option.name]
346
def checkoption(self, key):
347
if not isinstance(self.data[key], Option):
348
raise PersistConfigError("Option %s is not a valid option" % key)
351
def set(self, key, value, throw=False):
352
if type(value)==unicode:
355
self.checkoption(key)
356
except (KeyError, PersistConfigError):
359
self.define(key, value)
360
self.handle_callback('change', key, value)
362
self.data[key].value = value
363
self.handle_callback('change', key, value)
366
def append(self, key, value):
367
self.checkoption(key)
368
self.data[key].value.append(value)
370
self.handle_callback('change', key, value)
371
self.handle_callback('add', key, value)
373
def remove(self, key, value):
374
self.checkoption(key)
375
self.data[key].value.remove(value)
377
self.handle_callback('change', key, value)
378
self.handle_callback('remove', key, value)
380
def clear(self, key):
381
self.checkoption(key)
382
self.data[key].value = []
384
self.handle_callback('change', key, [])
385
self.handle_callback('clear', key)
387
def get(self, key, default=None):
389
return self.data[key].value
393
def has_key(self, key):
394
return self.data.has_key(key)
396
def callback(self, key, event, callback, extra_data=None):
397
callbacks = ["change", "add", "remove", "clear"]
398
if not event in callbacks:
399
raise PersistConfigError("Unsupported callback event %s" % event)
400
self.__callbacks[(key, event)] = (callback, extra_data)
402
def syncold(self, filename, remove=False):
403
""" sync with old config data """
404
if os.path.isfile(filename):
405
synckey = "persistconfig-syncold"
406
oldconfig = Persist(filename)
407
if not oldconfig.data.has_key(synckey):
408
rlog(10, 'persistconfig', "syncing old config %s with \
409
persistconfig" % filename)
410
for i, j in oldconfig.data.iteritems():
413
if j and not self.get(i):
414
self.set(i, oldconfig.data[i])
415
oldconfig.data[synckey] = time.localtime()