~ubuntu-branches/ubuntu/quantal/gozerbot/quantal

« back to all changes in this revision

Viewing changes to build/lib/gozerbot/compat/persistconfig.py

  • Committer: Bazaar Package Importer
  • Author(s): Jeremy Malcolm
  • Date: 2010-09-29 18:20:02 UTC
  • mfrom: (3.1.7 sid)
  • Revision ID: james.westby@ubuntu.com-20100929182002-gi532gnem1vlu6jy
Tags: 0.9.1.3-5
Added python2.5 build dependency. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# gozerbot/persistconfig.py
 
2
#
 
3
#
 
4
 
 
5
""" allow data to be pickled to disk .. creating the persisted object
 
6
    restores data
 
7
    
 
8
usage:
 
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
 
16
    
 
17
todo:
 
18
    - plugin api (more work needed?)
 
19
    
 
20
"""
 
21
 
 
22
__copyright__ = 'this file is in the public domain'
 
23
__author__ = 'Bas van Oostveen'
 
24
 
 
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
 
32
 
 
33
class Option(object):
 
34
 
 
35
    def __init__(self, value, desc, perm, example, name, exposed):
 
36
        assert isinstance(name, str), "Option.self.name must be a string"
 
37
        self.value = value
 
38
        self.desc = desc
 
39
        self.perm = perm
 
40
        self.example = example
 
41
        self.name = name.lower()
 
42
        self.exposed = exposed
 
43
 
 
44
    def __casattr__(self, name, val):
 
45
        # Update option if needed
 
46
        if not hasattr(self, name):
 
47
            setattr(self, name, val)
 
48
            return True
 
49
        else:
 
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)
 
53
                return True
 
54
            return False
 
55
 
 
56
    def check(self, key, plugname, value, desc, perm, example, name, exposed):
 
57
        upd = False
 
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
 
64
        if self.name == None:
 
65
            self.name = "%s-cfg-%s" % (plugname, str(key))
 
66
            upd = True
 
67
        return upd
 
68
 
 
69
    def __lt__(self, other):
 
70
        return self.value < other
 
71
 
 
72
    def __le__(self, other):
 
73
        return self.value <= other
 
74
 
 
75
    def __eq__(self, other):
 
76
        return self.value == other
 
77
 
 
78
    def __ne__(self, other):
 
79
        return self.value != other
 
80
 
 
81
    def __gt__(self, other):
 
82
        return self.value >= other
 
83
 
 
84
    def __ge__(self, other):
 
85
        return self.value >= other
 
86
 
 
87
class LazyValueDict(object):
 
88
 
 
89
    """ emulates the normal Persist.data (key, value) dict """
 
90
 
 
91
    def __init__(self, cfg):
 
92
        self.__cfg = cfg
 
93
 
 
94
    def __len__(self):
 
95
        return len(self.__persistData)
 
96
 
 
97
    def __getitem__(self, key):
 
98
        return self.__cfg.data[key].value
 
99
 
 
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)
 
106
 
 
107
    def __delitem__(self, key):
 
108
        raise Exception("Direct deletion not supported, use \
 
109
persistConfig.undefine()")
 
110
 
 
111
    def __iter__(self):
 
112
        return self.__cfg.data.__iter__()
 
113
 
 
114
    def iterkeys(self):
 
115
        return self.__iter__()
 
116
 
 
117
    def __contains__(self, item):
 
118
        return self.__cfg.data.has_key(item)
 
119
 
 
120
class PersistConfigError(Exception): pass
 
121
 
 
122
class PersistConfig(Persist):
 
123
 
 
124
    """ persist plugin configuration and create default handlers """
 
125
 
 
126
    def __init__(self):
 
127
        self.__basename__ = self.__class__.__name__
 
128
        self.plugname = calledfrom(sys._getframe())
 
129
        Persist.__init__(self, os.path.join(datadir, "%s-config" % \
 
130
self.plugname), {})
 
131
        self.__callbacks = {}
 
132
        cmndname = "%s-cfg" % self.plugname
 
133
        rlog(-3, 'persistconfig', 'added command %s (%s)' % (cmndname, \
 
134
self.plugname))
 
135
        cmnds[cmndname] = Command(self.cmnd_cfg, 'OPER', self.plugname, \
 
136
threaded=True)  
 
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)
 
142
 
 
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 
 
152
            # to option.value
 
153
            return LazyValueDict(self)
 
154
        return super(PersistConfig, self).__getattribute__(name)
 
155
 
 
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)]
 
159
            if callable(cb):
 
160
                cb(key, value, event, extra_data)
 
161
            else:
 
162
                rlog(5, 'persistconfig', 'invalid callback for %s %s' % (key, \
 
163
event))
 
164
                del self.__callbacks[(key, event)]
 
165
 
 
166
    ### cmnds
 
167
 
 
168
    def show_cfg(self, bot, ievent):
 
169
        s = []
 
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' % \
 
173
key)
 
174
                continue
 
175
            if not option.exposed:
 
176
                continue
 
177
            v = option.value
 
178
            if type(v) in [str, unicode]:
 
179
                v = '"'+v+'"'
 
180
            v = str(v)
 
181
            s.append("%s=%s" % (key, v))
 
182
        ievent.reply("options: " + ' .. '.join(s))
 
183
 
 
184
    def cmnd_cfgsave(self, bot, ievent):
 
185
        self.save()
 
186
        ievent.reply("config saved")
 
187
        
 
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("]"):
 
191
                values = []
 
192
                for v in ' '.join(args)[1:-1].replace(", ", ",").split(","):
 
193
                    if v[0]=='"' and v[-1]=='"':
 
194
                        # string
 
195
                        v = v.replace('"', '')
 
196
                    elif v[0]=="'" and v[-1]=="'":
 
197
                        # string
 
198
                        v = v.replace("'", "")
 
199
                    elif '.' in v:
 
200
                        # float
 
201
                        try:
 
202
                            v = float(v)
 
203
                        except ValueError:
 
204
                            ievent.reply("invalid long literal: %s" % v)
 
205
                            return
 
206
                    else:
 
207
                        # int
 
208
                        try:
 
209
                            v = int(v)
 
210
                        except ValueError:
 
211
                            ievent.reply("invalid int literal: %s" % v)
 
212
                            return
 
213
                    values.append(v)
 
214
                self.set(key, values)
 
215
                ievent.reply("%s set %s" % (key, values))
 
216
                return
 
217
            command = args[0]
 
218
            value = ' '.join(args[1:])
 
219
            if command == "clear":
 
220
                self.clear(key)
 
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":
 
226
                try:
 
227
                    self.remove(key, value)
 
228
                    ievent.reply("%s removed" % str(value))
 
229
                except ValueError:
 
230
                    ievent.reply("%s is not in list" % str(value))
 
231
            else:
 
232
                ievent.reply("invalid command")
 
233
            return
 
234
        else:
 
235
            value = ' '.join(args)
 
236
            try:
 
237
                value = type(option.value)(value)
 
238
            except:
 
239
                pass
 
240
            if type(value) == type(option.value):
 
241
                self.set(key, 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
 
246
                self.set(key, value)
 
247
                ievent.reply("%s set" % key)
 
248
            else:
 
249
                ievent.reply("value %s (%s) is not of the same type as %s \
 
250
(%s)" % (value, type(value), option.value, type(option.value)))
 
251
    
 
252
    def cmnd_cfg(self, bot, ievent):
 
253
        if not ievent.args:
 
254
            self.show_cfg(bot, ievent)
 
255
            return
 
256
        argc = len(ievent.args)
 
257
        key = ievent.args[0]
 
258
        try:
 
259
            option = self.data[key]
 
260
        except KeyError:
 
261
            ievent.reply("%s option %s not found" % (self.plugname, key))
 
262
            return
 
263
        if not isinstance(option, Option):
 
264
            rlog(5, 'persistconfig', 'Option %s is not a valid option' % key)
 
265
            return
 
266
        if not option.exposed:
 
267
            return
 
268
        if argc == 1:
 
269
            ievent.reply(str(option.value))
 
270
            return
 
271
        self.cmnd_cfg_edit(bot, ievent, ievent.args[1:], key, option)
 
272
 
 
273
    def generic_cmnd(self, key):
 
274
        def func(bot, ievent):
 
275
            try:
 
276
                option = self.data[key]
 
277
            except KeyError:
 
278
                ievent.reply("%s not found" % key)
 
279
                # need return ?
 
280
            if not isinstance(option, Option):
 
281
                rlog(5, 'persistconfig', 'Option %s is not a valid option' % \
 
282
key)
 
283
                return
 
284
            if ievent.args:
 
285
                value = ' '.join(ievent.args)
 
286
                try:
 
287
                    value = type(option.value)(value)
 
288
                except:
 
289
                    pass
 
290
                self.cmnd_cfg_edit(bot, ievent, ievent.args, key, option)
 
291
            else:
 
292
                ievent.reply(str(option.value))
 
293
        return func
 
294
 
 
295
    ### plugin api
 
296
 
 
297
    def define(self, key, value=None, desc="plugin option", perm='OPER', \
 
298
example="", name=None, exposed=True):
 
299
        if name:
 
300
            name = name.lower()
 
301
        if not self.data.has_key(key):
 
302
            if name == None:
 
303
                name = "%s-cfg-%s" % (self.plugname, str(key))
 
304
            option = Option(value, desc, perm, example, name, exposed)
 
305
            self.data[key] = option
 
306
            self.save()
 
307
        else:
 
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 
 
312
            # has to be changed
 
313
            if not isinstance(option, Option):
 
314
                if name == None:
 
315
                    name = "%s-cfg-%s" % (self.plugname, str(key))
 
316
                if type(value) == type(option):
 
317
                    value = option
 
318
                option = Option(value, desc, perm, example, name, exposed)
 
319
                self.data[key] = option
 
320
                self.save()
 
321
            else:
 
322
                if type(option.value) == types.LongType and \
 
323
type(value) == types.IntType:
 
324
                    value = long(value)
 
325
                if option.check(key, self.plugname, value, desc, perm, \
 
326
example, name, exposed):
 
327
                    self.data[key] = option
 
328
                    self.save()
 
329
        
 
330
    def undefine(self, key, throw=False):
 
331
        try:
 
332
            option = self.data[key]
 
333
            if option.exposed:
 
334
                if cmnds.has_key(option.name):
 
335
                    del cmnds[option.name]
 
336
                if examples.has_key(option.name):
 
337
                    del examples[option.name]
 
338
            del self.data[key]
 
339
            self.save()
 
340
            return True
 
341
        except KeyError, e:
 
342
            if throw:
 
343
                raise
 
344
        return False
 
345
 
 
346
    def checkoption(self, key):
 
347
        if not isinstance(self.data[key], Option):
 
348
            raise PersistConfigError("Option %s is not a valid option" % key)
 
349
        return True
 
350
 
 
351
    def set(self, key, value, throw=False):
 
352
        if type(value)==unicode:
 
353
            value = str(value)
 
354
        try:
 
355
            self.checkoption(key)
 
356
        except (KeyError, PersistConfigError):
 
357
            if throw:
 
358
                raise
 
359
            self.define(key, value)
 
360
            self.handle_callback('change', key, value)
 
361
        else:
 
362
            self.data[key].value = value
 
363
            self.handle_callback('change', key, value)
 
364
            self.save()
 
365
 
 
366
    def append(self, key, value):
 
367
        self.checkoption(key)
 
368
        self.data[key].value.append(value)
 
369
        self.save()
 
370
        self.handle_callback('change', key, value)
 
371
        self.handle_callback('add', key, value)
 
372
 
 
373
    def remove(self, key, value):
 
374
        self.checkoption(key)
 
375
        self.data[key].value.remove(value)
 
376
        self.save()
 
377
        self.handle_callback('change', key, value)
 
378
        self.handle_callback('remove', key, value)
 
379
 
 
380
    def clear(self, key):
 
381
        self.checkoption(key)
 
382
        self.data[key].value = []
 
383
        self.save()
 
384
        self.handle_callback('change', key, [])
 
385
        self.handle_callback('clear', key)
 
386
 
 
387
    def get(self, key, default=None):
 
388
        try:
 
389
            return self.data[key].value
 
390
        except KeyError:
 
391
            return default
 
392
 
 
393
    def has_key(self, key):
 
394
        return self.data.has_key(key)
 
395
 
 
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)
 
401
    
 
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():
 
411
                    if i == synckey:
 
412
                        continue
 
413
                    if j and not self.get(i):
 
414
                        self.set(i, oldconfig.data[i])
 
415
                oldconfig.data[synckey] = time.localtime()
 
416
                oldconfig.save()
 
417
            del oldconfig
 
418
            if remove:
 
419
                os.unlink(filename)
 
420