import os import itertools from zope.interface import classProvides from twisted.python import usage from twisted.python.reflect import namedAny from twisted.cred import portal from twisted import plugin from epsilon import extime from axiom import iaxiom from axiom.dependency import installOn from axiom.scripts import axiomatic from xmantissa import publicweb from circus import qdb, icircus class Import(usage.Options, axiomatic.AxiomaticSubCommandMixin): longdesc = ''' Import a quote from files in an implementation-defined format. If a quote already exists with the same qid, an error will occur.''' optParameters = [ ('files', 'f', None, 'File containing a line-delimited list of quote files'), ] def importQuoteFromFile(self, store, fn): fp = file(fn, 'rb') qid = int(fp.readline()) rating = int(fp.readline()) votes = int(fp.readline()) votesFor = int(fp.readline()) votesAgainst = int(fp.readline()) if rating != votesFor - votesAgainst or votes != votesFor + votesAgainst: print 'quote inconsistent: %r' % (fn,) assert (votes + rating) % 2 == 0 votesFor = (votes + rating) / 2 votesAgainst = (votes - rating) / 2 assert rating == votesFor - votesAgainst assert votes == votesFor + votesAgainst added = extime.Time.fromPOSIXTimestamp(float(fp.readline())) content = unicode(fp.read(), 'utf-8') for quote in store.query(qdb.Quote, qdb.Quote.qid == qid): raise usage.UsageError('Quote with qid %d already exists' % qid) quote = qdb.Quote(store=store, qid=qid, rating=rating, votes=votes, votesFor=votesFor, votesAgainst=votesAgainst, added=added, content=content) def resetQuoteCounter(self, store): for lastQuote in store.query(qdb.Quote, limit=1, sort=qdb.Quote.qid.desc): icircus.IQuoteDatabase(store).lastQid = lastQuote.qid def postOptions(self): if self['files'] is None: raise usage.UsageError('--files must be specified') def _getAppStore(store): realm = portal.IRealm(store) account = realm.accountByAddress(u'FlyingCircus', None) appStore = account.avatars.open() return appStore def _importQuotes(appStore): for fn in file(self['files'], 'rb'): fn = fn.rstrip('\n') self.importQuoteFromFile(appStore, fn) self.resetQuoteCounter(appStore) store = self.parent.getStore() appStore = store.transact(_getAppStore, store) appStore.transact(_importQuotes, appStore) class Export(usage.Options, axiomatic.AxiomaticSubCommandMixin): longdesc = ''' Export a quote database .''' optParameters = [ ('path', 'p', None, 'Path to use as a base for files when exporting a quote database'), ] def exportQuoteToFile(self, quote, fd): lines = [ '%s\n' % (quote.qid), '%s\n' % (quote.rating), '%s\n' % (quote.votes), '%s\n' % (quote.votesFor), '%s\n' % (quote.votesAgainst), '%s\n' % (quote.added.asPOSIXTimestamp()), quote.content.encode('utf-8')] fd.writelines(lines) def postOptions(self): if self['path'] is None: raise usage.UsageError('--path must be specified') counter = itertools.count() os.makedirs(self['path']) def _qfn(quote): return os.path.join(self['path'], 'qdb-q-%s' % (counter.next(),)) def _getAppStore(store): realm = portal.IRealm(store) account = realm.accountByAddress(u'FlyingCircus', None) appStore = account.avatars.open() return appStore def _exportQuotes(appStore): dbfn = os.path.join(self['path'], 'qdb') dbfd = file(dbfn, 'wb') for quote in appStore.query(qdb.Quote): qfn = _qfn(quote) self.exportQuoteToFile(quote, file(qfn, 'wb')) dbfd.write(qfn + '\n') print 'Database directory written to %r.' % (dbfn,) store = self.parent.getStore() appStore = store.transact(_getAppStore, store) appStore.transact(_exportQuotes, appStore) class SetupCommands(axiomatic.AxiomaticSubCommand): longdesc = 'Setup stuff' optParameters = [ ('frontpage', None, None, 'Fully qualified Python name for a FrontPage replacement'), ('vhost', None, None, 'Fully qualified Python name for a VHost thing'), ] def postOptions(self): s = self.parent.getStore() frontpage = self.get('frontpage') if frontpage is not None: s.transact(self.replaceFrontPage, s, namedAny(frontpage)) vhost = self.get('vhost') if vhost is not None: s.transact(self.setupVHost, s, namedAny(vhost)) def setupVHost(self, store, vhostType): store.query(vhostType).deleteFromStore() vh = store.findOrCreate(vhostType) installOn(vh, store) def replaceFrontPage(self, store, frontpageType): store.query(publicweb.FrontPage).deleteFromStore() store.query(frontpageType).deleteFromStore() fp = store.findOrCreate(frontpageType, prefixURL=u'') installOn(fp, store) class Clowns(axiomatic.AxiomaticSubCommand): longdesc = 'Do stuff with the clowns' def postOptions(self): realm = portal.IRealm(self.parent.getStore()) account = realm.accountByAddress(u'FlyingCircus', None) appStore = account.avatars.open() appStore.query(qdb.Clown).deleteFromStore() for quote in appStore.query(qdb.Quote): qdb.createParticipants(quote) class CircusConfiguration(usage.Options, axiomatic.AxiomaticSubCommandMixin): classProvides(plugin.IPlugin, iaxiom.IAxiomaticCommand) name = 'flyingcircus' description = 'Circus of flying IRC quotes' subCommands = [ ('import', None, Import, 'Import a quote from a file in an implementation-defined format.'), ('export', None, Export, 'Export a quote database to an implementation-defined format.'), ('setup', None, SetupCommands, 'Setup a FlyingCircus site'), ('clowns', None, Clowns, 'Train the clowns'), ] def getStore(self): return self.parent.getStore()