Aka: Add lock support.

This commit is contained in:
Valentin Lorentz 2013-07-23 22:47:50 +02:00
parent b7a9569f72
commit c49e088879
2 changed files with 118 additions and 22 deletions

View File

@ -31,6 +31,7 @@
import re import re
import os import os
import sys import sys
import datetime
import supybot.utils as utils import supybot.utils as utils
from supybot.commands import * from supybot.commands import *
@ -54,17 +55,22 @@ if sqlalchemy:
__tablename__ = 'aliases' __tablename__ = 'aliases'
id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True) id = sqlalchemy.Column(sqlalchemy.Integer, primary_key=True)
name = sqlalchemy.Column(sqlalchemy.String, unique=True) name = sqlalchemy.Column(sqlalchemy.String, unique=True, nullable=False)
alias = sqlalchemy.Column(sqlalchemy.String) alias = sqlalchemy.Column(sqlalchemy.String, nullable=False)
locked = sqlalchemy.Column(sqlalchemy.Boolean, nullable=False)
locked_by = sqlalchemy.Column(sqlalchemy.String, nullable=True)
locked_at = sqlalchemy.Column(sqlalchemy.DateTime, nullable=True)
def __init__(self, name, alias): def __init__(self, name, alias):
self.name = name self.name = name
self.alias = alias self.alias = alias
self.locked = False
self.locked_by = None
self.locked_at = None
def __repr__(self): def __repr__(self):
return "<Alias('%r', '%r')>" % (self.name, self.alias) return "<Alias('%r', '%r')>" % (self.name, self.alias)
# TODO: Add table for locks
# TODO: Add table for usage statistics # TODO: Add table for usage statistics
class SqlAlchemyAkaDB(object): class SqlAlchemyAkaDB(object):
@ -110,7 +116,7 @@ if sqlalchemy:
def add_aka(self, channel, name, alias): def add_aka(self, channel, name, alias):
if self.has_aka(channel, name): if self.has_aka(channel, name):
raise AliasError(_('This Aka already exists.')) raise AkaError(_('This Aka already exists.'))
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
if isinstance(name, str): if isinstance(name, str):
name = name.decode('utf8') name = name.decode('utf8')
@ -125,6 +131,43 @@ if sqlalchemy:
db.query(Alias).filter(Alias.name == name).delete() db.query(Alias).filter(Alias.name == name).delete()
db.commit() db.commit()
def lock_aka(self, channel, name, by):
db = self.get_db(channel)
try:
aka = db.query(Alias) \
.filter(Alias.name == name).one()
except sqlalchemy.orm.exc.NoResultFound:
raise AkaError(_('This Aka does not exist'))
if aka.locked:
raise AkaError(_('This Aka is already locked.'))
aka.locked = True
aka.locked_by = by
aka.locked_at = datetime.datetime.now()
db.commit()
def unlock_aka(self, channel, name, by):
db = self.get_db(channel)
try:
aka = db.query(Alias.alias) \
.filter(Alias.name == name).one()
except sqlalchemy.orm.exc.NoResultFound:
raise AkaError(_('This Aka does not exist'))
if aka.locked:
raise AkaError(_('This Aka is already unlocked.'))
aka.locked = False
aka.locked_by = by
aka.locked_at = datetime.datetime.now()
db.commit()
def get_aka_lock(self, channel, name):
try:
return self.get_db(channel) \
.query(Alias.locked, Alias.locked_by, Alias.locked_at)\
.filter(Alias.name == name).one()
except sqlalchemy.orm.exc.NoResultFound:
raise AkaError(_('This Aka does not exist'))
def getArgs(args, required=1, optional=0, wildcard=0): def getArgs(args, required=1, optional=0, wildcard=0):
if len(args) < required: if len(args) < required:
raise callbacks.ArgumentError raise callbacks.ArgumentError
@ -138,10 +181,10 @@ def getArgs(args, required=1, optional=0, wildcard=0):
ret = list(args) ret = list(args)
return ret return ret
class AliasError(Exception): class AkaError(Exception):
pass pass
class RecursiveAlias(AliasError): class RecursiveAlias(AkaError):
pass pass
dollarRe = re.compile(r'\$(\d+)') dollarRe = re.compile(r'\$(\d+)')
@ -253,20 +296,24 @@ class Aka(callbacks.Plugin):
def _add_aka(self, channel, name, alias): def _add_aka(self, channel, name, alias):
if self.__parent.isCommandMethod(name): if self.__parent.isCommandMethod(name):
raise AliasError(_('You can\'t overwrite commands in ' raise AkaError(_('You can\'t overwrite commands in '
'this plugin.')) 'this plugin.'))
if self._db.has_aka(channel, name): if self._db.has_aka(channel, name):
raise AliasError(_('This Aka already exists.')) raise AkaError(_('This Aka already exists.'))
biggestDollar = findBiggestDollar(alias) biggestDollar = findBiggestDollar(alias)
biggestAt = findBiggestAt(alias) biggestAt = findBiggestAt(alias)
wildcard = '$*' in alias wildcard = '$*' in alias
if biggestAt and wildcard: if biggestAt and wildcard:
raise AliasError(_('Can\'t mix $* and optional args (@1, etc.)')) raise AkaError(_('Can\'t mix $* and optional args (@1, etc.)'))
if alias.count('$*') > 1: if alias.count('$*') > 1:
raise AliasError(_('There can be only one $* in an alias.')) raise AkaError(_('There can be only one $* in an alias.'))
self._db.add_aka(channel, name, alias) self._db.add_aka(channel, name, alias)
def _remove_aka(self, channel, name): def _remove_aka(self, channel, name, evenIfLocked=False):
if not evenIfLocked:
(locked, by, at) = self._db.get_aka_lock(channel, name)
if locked:
raise AkaError(_('This Aka is locked.'))
self._db.remove_aka(channel, name) self._db.remove_aka(channel, name)
def add(self, irc, msg, args, optlist, name, alias): def add(self, irc, msg, args, optlist, name, alias):
@ -294,27 +341,75 @@ class Aka(callbacks.Plugin):
self.log.info('Adding Aka %q for %q (from %s)', self.log.info('Adding Aka %q for %q (from %s)',
name, alias, msg.prefix) name, alias, msg.prefix)
irc.replySuccess() irc.replySuccess()
except AliasError as e: except AkaError as e:
irc.error(str(e)) irc.error(str(e))
add = wrap(add, [getopts({ add = wrap(add, [getopts({
'channel': 'somethingWithoutSpaces', 'channel': 'somethingWithoutSpaces',
}), 'commandName', 'text']) }), 'commandName', 'text'])
def remove(self, irc, msg, args, channel, name): def remove(self, irc, msg, args, optlist, name):
"""[<#channel|global>] <name> """[--channel <#channel>] <name>
Removes the given alias, if unlocked. Removes the given alias, if unlocked.
""" """
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not ircutils.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
try: try:
self._remove_aka(channel, name) self._remove_aka(channel, name)
self.log.info('Removing Aka %q (from %s)', name, msg.prefix) self.log.info('Removing Aka %q (from %s)', name, msg.prefix)
irc.replySuccess() irc.replySuccess()
except AliasError as e: except AkaError as e:
irc.error(str(e)) irc.error(str(e))
remove = wrap(remove, [first(('literal', 'global'), 'channel'), remove = wrap(remove, [getopts({
'commandName']) 'channel': 'somethingWithoutSpaces',
}), 'commandName'])
def lock(self, irc, msg, args, optlist, user, name):
"""[--channel <#channel>] <alias>
Locks an alias so that no one else can change it.
"""
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not ircutils.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
try:
self._db.lock_aka(channel, name, user)
except AkaError as e:
irc.error(str(e))
lock = wrap(lock, [getopts({
'channel': 'somethingWithoutSpaces',
}), 'admin', 'commandName'])
def unlock(self, irc, msg, args, optlist, user, name):
"""[--channel <#channel>] <alias>
Unlocks an alias so that people can define new aliases over it.
"""
channel = 'global'
for (option, arg) in optlist:
if option == 'channel':
if not ircutils.isChannel(arg):
irc.error(_('%r is not a valid channel.') % arg,
Raise=True)
channel = arg
try:
self._db.lock_aka(channel, name, user)
except AkaError as e:
irc.error(str(e))
unlock = wrap(unlock, [getopts({
'channel': 'somethingWithoutSpaces',
}), 'admin', 'commandName'])
Class = Aka Class = Aka

View File

@ -103,11 +103,12 @@ class AkaTestCase(ChannelPluginTestCase):
def testAddRemoveAka(self): def testAddRemoveAka(self):
cb = self.irc.getCallback('Aka') cb = self.irc.getCallback('Aka')
cb._add_aka(None, 'foobar', 'echo sbbone', lock=True) cb._add_aka('global', 'foobar', 'echo sbbone')
cb._db.lock_aka('global', 'foobar', 'evil_admin')
self.assertResponse('foobar', 'sbbone') self.assertResponse('foobar', 'sbbone')
self.assertRaises(Aka.AkaError, cb.removeAka, 'foobar') self.assertRaises(Aka.AkaError, cb._remove_aka, 'global', 'foobar')
cb._remove_aka(None, 'foobar', evenIfLocked=True) cb._remove_aka('global', 'foobar', evenIfLocked=True)
self.failIf('foobar' in cb.akaes) self.assertNotRegexp('list Aka', 'foobar')
self.assertError('foobar') self.assertError('foobar')
def testOptionalArgs(self): def testOptionalArgs(self):