root/axiom/plugins/eridanuscmd.py @ 196

Revision 196, 15.3 kB (checked in by Jonathan Jacobs <korpse@…>, 12 months ago)

Add a FTS indexer source for LinkDB comments.
Ignore-this: 79282e13ad924186c2f4c1db3c75b1d7

Line 
1from epsilon.extime import Time
2
3from twisted.cred.portal import IRealm
4from twisted.python.filepath import FilePath
5
6from axiom.scripts import axiomatic
7from axiom.dependency import installOn, uninstallFrom
8from axiom.attributes import AND
9
10from xmantissa import publicweb
11
12from eridanus import plugin, util
13from eridanus.bot import IRCBotService, IRCBotFactoryFactory, IRCBotConfig
14
15
16class ConfigureService(axiomatic.AxiomaticSubCommand):
17    longdesc = 'Configure an Eridanus service'
18
19    optParameters = [
20        ('id',       None, None, 'Service identifier'),
21        ('name',     None, None, 'Network name'),
22        ('host',     'h',  None, 'Service hostname'),
23        ('port',     'p',  6667, 'Service port number'),
24        ('nick',     'n',  None, 'Bot nickname'),
25        ('channels', 'c',  None, 'Channels to join'),
26        ('ignores',  'i',  None, 'Nicknames to ignore'),
27        ]
28
29    def getStore(self):
30        return self.parent.getStore()
31
32    def postOptions(self):
33        store = self.getStore()
34
35        svc = store.findUnique(IRCBotService, IRCBotService.serviceID == self['id'])
36        if svc.config is None:
37            svc.config = config = IRCBotConfig(store=store)
38        else:
39            config = svc.config
40
41        if self['name']:
42            config.name = self.decodeCommandLine(self['name'])
43        if self['host']:
44            config.hostname = self['host']
45        if self['port']:
46            config.portNumber = int(self['port'])
47        if self['nick']:
48            config.nickname = self.decodeCommandLine(self['nick'])
49        if self['channels']:
50            config.channels = self.decodeCommandLine(self['channels']).split(u',')
51        if self['ignores']:
52            config.ignores = self.decodeCommandLine(self['ignores']).split(u',')
53
54
55def createService(siteStore, serviceID):
56    fact = siteStore.findOrCreate(IRCBotFactoryFactory)
57    svc = siteStore.findOrCreate(IRCBotService,
58                             serviceID=serviceID,
59                             factory=fact)
60    try:
61        installOn(svc, siteStore)
62    except:
63        pass
64    return svc
65
66
67class CreateService(axiomatic.AxiomaticSubCommand):
68    longdesc = 'Create a new Eridanus service'
69
70    optParameters = [
71        ('id', None, None, 'Service identifier'),
72        ]
73
74    def getStore(self):
75        return self.parent.getStore()
76
77    def postOptions(self):
78        store = self.getStore()
79        createService(store, self['id'])
80
81
82class RemoveService(axiomatic.AxiomaticSubCommand):
83    longdesc = 'Remove an existing Eridanus service'
84
85    optParameters = [
86        ('id', None, None, 'Service identifier'),
87        ]
88
89    def getStore(self):
90        return self.parent.getStore()
91
92    def postOptions(self):
93        store = self.getStore()
94
95        fact = store.findUnique(IRCBotFactoryFactory)
96        svc = store.findUnique(IRCBotService,
97                               AND(IRCBotService.serviceID == self['id'],
98                                   IRCBotService.factory == fact));
99
100        uninstallFrom(svc, store)
101
102
103class ListServices(axiomatic.AxiomaticSubCommand):
104    longdesc = 'List available Eridanus services'
105
106    def getStore(self):
107        return self.parent.getStore()
108
109    def postOptions(self):
110        store = self.getStore()
111        print '\n'.join(store.query(IRCBotService).getColumn('serviceID'))
112
113
114class ManageServices(axiomatic.AxiomaticSubCommand):
115    longdesc = 'Manage Eridanus services'
116
117    subCommands = [
118        ('create', None, CreateService, 'Create a new service'),
119        ('remove', None, RemoveService, 'Remove an existing service'),
120        ('config', None, ConfigureService, 'Set service configuration data'),
121        ('list',   None, ListServices, 'List available services'),
122        ]
123
124    def getStore(self):
125        return self.parent.getStore()
126
127    def getAppStore(self):
128        return self.parent.getAppStore()
129
130
131# XXX: this shouldn't be anywhere near here
132from eridanusstd import linkdb
133from eridanusstd.linkdb import LinkManager, LinkEntry, LinkEntryComment, LinkEntryMetadata
134class ImportExportFile(object):
135    encoding = 'utf-8'
136
137    def __init__(self, fd, appStore):
138        self.fd = fd
139        self.appStore = appStore
140        self.eof = False
141        self.count = 0
142        self._typeConverters = {'bytes':     (self.writeline, self.readline),
143                                'text':      (self.writeText, self.readText),
144                                'timestamp': (self.writeTimestamp, self.readTimestamp),
145                                'integer':   (self.writeInteger, self.readInteger),
146                                'textlist':  (self.writeTextList, self.readTextList),
147                                'boolean':   (self.writeBoolean, self.readBoolean)}
148
149    def write(self, s):
150        self.fd.write(s)
151
152    def writeline(self, s):
153        self.write(s + '\n')
154
155    def readline(self):
156        self.count += 1
157        line = self.fd.readline()
158        if not line:
159            self.eof = True
160
161        return line.rstrip()
162
163    def writeInteger(self, value):
164        self.writeline(str(value))
165
166    def readInteger(self):
167        return int(self.readline())
168
169    def writeTimestamp(self, value):
170        self.writeline(str(value.asPOSIXTimestamp()))
171
172    def readTimestamp(self):
173        return Time.fromPOSIXTimestamp(float(self.readline()))
174
175    def writeText(self, value):
176        if value is None:
177            data = ''
178        else:
179            data = value.encode(self.encoding)
180        self.writeline(data)
181
182    def readText(self):
183        data = self.readline()
184        if not data:
185            return None
186        return data.decode(self.encoding)
187
188    def writeBoolean(self, value):
189        return self.writeline(str(int(value)))
190
191    def readBoolean(self):
192        return bool(int(self.readline()))
193
194    def writeTextList(self, value):
195        data = '\1'.join(t.encode(self.encoding) for t in value)
196        self.writeline(data)
197
198    def readTextList(self):
199        return [t.decode(self.encoding) for t in self.readline().split('\1')]
200
201    def writeItem(self, item, attrs):
202        for attrName in attrs:
203             typeName = getattr(type(item), attrName).__class__.__name__
204             writer = self._typeConverters[typeName][0]
205             writer(getattr(item, attrName))
206
207    def readItem(self, itemType, attrs):
208        def _readAttributes():
209            for attrName in attrs:
210                typeName = getattr(itemType, attrName).__class__.__name__
211                reader = self._typeConverters[typeName][1]
212                yield attrName, reader()
213
214        return dict(_readAttributes())
215
216    serviceAttrs = ['serviceID']
217
218    def writeService(self, service):
219        self.writeline('service')
220        self.writeItem(service, self.serviceAttrs)
221
222        self.writeConfig(service.config)
223
224        for manager in linkdb.getAllLinkManagers(self.appStore, service.serviceID):
225            self.writeEntryManager(manager)
226
227    def readService(self):
228        return self.readItem(IRCBotService, self.serviceAttrs)
229
230    configAttrs = ['name', 'hostname', 'portNumber', 'nickname', 'channels', 'ignores']
231
232    def writeConfig(self, config):
233        self.writeline('config')
234        self.writeItem(config, self.configAttrs)
235
236    def readConfig(self):
237        return self.readItem(IRCBotConfig, self.configAttrs)
238
239    entryManagerAttrs = ['channel', 'lastEid']
240
241    def writeEntryManager(self, entryManager):
242        self.writeline('entrymanager')
243        self.writeItem(entryManager, self.entryManagerAttrs)
244
245        for entry in entryManager.getEntries(discarded=None, deleted=None):
246            self.writeEntry(entry)
247
248    def readEntryManager(self):
249        return self.readItem(LinkManager, self.entryManagerAttrs)
250
251    entryAttrs = ['eid', 'created', 'modified', 'channel', 'nick', 'url', 'title', 'occurences', 'isDiscarded', 'isDeleted']
252
253    def writeEntry(self, entry):
254        self.writeline('entry')
255        self.writeItem(entry, self.entryAttrs)
256
257        for comment in entry.getComments():
258            self.writeComment(comment)
259
260        for metadata in entry._getMetadata():
261            self.writeMetadata(metadata)
262
263    def readEntry(self):
264        return self.readItem(LinkEntry, self.entryAttrs)
265
266    commentAttrs = ['created', 'nick', 'comment', 'initial']
267
268    def writeComment(self, comment):
269        self.writeline('comment')
270        self.writeItem(comment, self.commentAttrs)
271
272    def readComment(self):
273        return self.readItem(LinkEntryComment, self.commentAttrs)
274
275    metadataAttrs = ['kind', 'data']
276
277    def writeMetadata(self, metadata):
278        self.writeline('metadata')
279        self.writeItem(metadata, self.metadataAttrs)
280
281    def readMetadata(self):
282        return self.readItem(LinkEntryMetadata, self.metadataAttrs)
283
284
285# XXX: this shouldn't be anywhere near here
286class ExportEntries(axiomatic.AxiomaticSubCommand):
287    longdesc = 'Export linkdb entries to disk'
288
289    optParameters = [
290        ('path', 'p', None, 'Path to output export data to'),
291        ]
292
293    def getStore(self):
294        return self.parent.getStore()
295
296    def getAppStore(self):
297        return self.parent.getAppStore()
298
299    def postOptions(self):
300        appStore = self.getAppStore()
301        store = self.getStore()
302
303        outroot = FilePath(self['path'])
304        if not outroot.exists():
305            outroot.makedirs()
306
307        for i, service in enumerate(store.query(IRCBotService)):
308            print 'Processing service %r...' % (service.serviceID,)
309
310            fd = outroot.child(str(i)).open('wb')
311            ief = ImportExportFile(fd, appStore)
312            ief.writeService(service)
313
314
315# XXX: this shouldn't be anywhere near here
316class ImportEntries(axiomatic.AxiomaticSubCommand):
317    longdesc = 'Import linkdb entries from an export'
318
319    optFlags = [
320        ('clear', None, 'Remove existing entries before performing the import'),
321        ]
322
323    optParameters = [
324        ('path', 'p', None, 'Path to read export data from'),
325        ]
326
327    def getStore(self):
328        return self.parent.getStore()
329
330    def getAppStore(self):
331        return self.parent.getAppStore()
332
333    def postOptions(self):
334        appStore = self.getAppStore()
335        siteStore = self.getStore()
336
337        inroot = FilePath(self['path'])
338
339        availableModes = ['service', 'config', 'entrymanager', 'entry', 'comment', 'metadata']
340
341        if self['clear']:
342            appStore.query(LinkEntryComment).deleteFromStore()
343            appStore.query(LinkEntryMetadata).deleteFromStore()
344            appStore.query(LinkEntry).deleteFromStore()
345            appStore.query(LinkManager).deleteFromStore()
346
347        mode = None
348        service = None
349        config = None
350        entryManager = None
351        entry = None
352
353        for fp in inroot.globChildren('*'):
354            fd = fp.open()
355            ief = ImportExportFile(fd, appStore)
356
357            while True:
358                line = ief.readline()
359                if ief.eof:
360                    break
361
362                if line in availableModes:
363                    mode = line
364
365                if mode == 'service':
366                    kw = ief.readService()
367                    service = createService(siteStore, **kw)
368                elif mode == 'config':
369                    kw = ief.readConfig()
370                    service.config = config = IRCBotConfig(store=siteStore, **kw)
371                elif mode == 'entrymanager':
372                    assert service is not None
373                    kw = ief.readEntryManager()
374                    print 'Creating entry manager for %(channel)s...' % kw
375                    entryManager = LinkManager(store=appStore, serviceID=service.serviceID, **kw)
376                    if self['clear']:
377                        entryManager.searchIndexer.reset()
378                elif mode == 'entry':
379                    assert entryManager is not None
380                    kw = ief.readEntry()
381                    #print 'Creating entry #%(eid)s for %(channel)s...' % kw
382                    entry = LinkEntry(store=appStore, **kw)
383                elif mode == 'comment':
384                    assert entry is not None
385                    kw = ief.readComment()
386                    LinkEntryComment(store=appStore, parent=entry, **kw)
387                elif mode == 'metadata':
388                    assert entry is not None
389                    kw = ief.readMetadata()
390                    LinkEntryMetadata(store=appStore, entry=entry, **kw)
391
392
393class InstallPlugin(axiomatic.AxiomaticSubCommand):
394    longdesc = 'Install a plugin'
395
396    synopsis = '<pluginName>'
397
398    def getStore(self):
399        return self.parent.getStore()
400
401    def getAppStore(self):
402        return self.parent.getAppStore()
403
404    def parseArgs(self, pluginName):
405        self['pluginName'] = self.decodeCommandLine(pluginName)
406
407    def postOptions(self):
408        plugin.installPlugin(self.getAppStore(), self['pluginName'])
409
410
411class GrantPlugin(axiomatic.AxiomaticSubCommand):
412    longdesc = 'Grant a user with access to a plugin'
413
414    synopsis = '<user> <domain> <pluginName>'
415
416    def parseArgs(self, username, domain, pluginName):
417        self['username'] = self.decodeCommandLine(username)
418        self['domain'] = self.decodeCommandLine(domain)
419        self['pluginName'] = self.decodeCommandLine(pluginName)
420
421    def postOptions(self):
422        loginSystem = self.parent.getLoginSystem()
423        loginAccount = loginSystem.accountByAddress(self['username'], self['domain'])
424        assert loginAccount is not None, 'No such user'
425        userStore = loginAccount.avatars.open()
426        plugin.installPlugin(userStore, self['pluginName'])
427
428
429class ManagePlugins(axiomatic.AxiomaticSubCommand):
430    longdesc = 'Manage plugins'
431
432    subCommands = [
433        ('install', None, InstallPlugin, 'Install a plugin'),
434        ('grant', None, GrantPlugin, 'Endow a user with access to a plugin'),
435        ]
436
437    def getStore(self):
438        return self.parent.getStore()
439
440    def getAppStore(self):
441        return self.parent.getAppStore()
442
443    def getLoginSystem(self):
444        return self.parent.getLoginSystem()
445
446
447class Hackery(axiomatic.AxiomaticSubCommand):
448    longdesc = 'Beware, thar be hacks!'
449
450    def postOptions(self):
451        from axiom.scheduler import Scheduler
452        from xmantissa.fulltext import SQLiteIndexer
453        from xmantissa.ixmantissa import IFulltextIndexer
454        from eridanusstd.linkdb import LinkEntrySource, LinkEntryCommentSource
455        store = self.parent.getAppStore()
456
457        #scheduler = Scheduler(store=store)
458        #installOn(scheduler, store)
459       
460        print 'Deleting old indexers...'
461        store.query(SQLiteIndexer).deleteFromStore()
462        print 'Creating new indexer...'
463        indexer = SQLiteIndexer(store=store)
464        store.powerUp(indexer, IFulltextIndexer)
465
466        entrySource = store.findOrCreate(LinkEntrySource)
467        indexer.addSource(entrySource)
468        commentSource = store.findOrCreate(LinkEntryCommentSource)
469        indexer.addSource(commentSource)
470
471
472class Eridanus(axiomatic.AxiomaticCommand):
473    name = 'eridanus'
474    description = 'Eridanus mechanic'
475
476    subCommands = [
477        ('service', None, ManageServices, 'Manage services'),
478        ('export',  None, ExportEntries,  'Export entries'),
479        ('import',  None, ImportEntries,  'Import entries'),
480        ('plugins', None, ManagePlugins,  'Manage plugins'),
481        ('hackery', None, Hackery,        'Perform magic'),
482        ]
483
484    def getStore(self):
485        return self.parent.getStore()
486
487    def getAppStore(self):
488        return self.getLoginSystem().accountByAddress(u'Eridanus', None).avatars.open()
489
490    def getLoginSystem(self):
491        return IRealm(self.getStore())
Note: See TracBrowser for help on using the browser.