Add context menu support
This patch adds support for adding context menu entries to connected users. As these callbacks are - in contrast to all others in mumo - user specific they do not fit well with the existing mumo architecture. To cope with this creating these callbacks is now handled directly inside the manager on behalf of the remote manager. The remote manager then handles forwarding any context menu actions to a user selected handler. This patch also removes the vestigial parts of context menu handling already present but unused in mumo as they were an architectural dead end.
This commit is contained in:
159
mumo.py
159
mumo.py
@@ -63,7 +63,7 @@ default.update({'ice':(('host', str, '127.0.0.1'),
|
||||
('watchdog', int, 30),
|
||||
('callback_host', str, '127.0.0.1'),
|
||||
('callback_port', int, -1)),
|
||||
|
||||
|
||||
'iceraw':None,
|
||||
'murmur':(('servers', commaSeperatedIntegers, []),),
|
||||
'system':(('pidfile', str, 'mumo.pid'),),
|
||||
@@ -71,13 +71,13 @@ default.update({'ice':(('host', str, '127.0.0.1'),
|
||||
('file', str, 'mumo.log'))})
|
||||
|
||||
def load_slice(slice):
|
||||
#
|
||||
#
|
||||
#--- Loads a given slicefile, used by dynload_slice and fsload_slice
|
||||
# This function works around a number of differences between Ice python
|
||||
# versions and distributions when it comes to slice include directories.
|
||||
#
|
||||
fallback_slicedirs = ["-I" + sdir for sdir in cfg.ice.slicedirs.split(';')]
|
||||
|
||||
|
||||
if not hasattr(Ice, "getSliceDir"):
|
||||
Ice.loadSlice('-I%s %s' % (" ".join(fallback_slicedirs), slice))
|
||||
else:
|
||||
@@ -86,7 +86,7 @@ def load_slice(slice):
|
||||
slicedirs = fallback_slicedirs
|
||||
else:
|
||||
slicedirs = ['-I' + slicedir]
|
||||
|
||||
|
||||
Ice.loadSlice('', slicedirs + [slice])
|
||||
|
||||
def dynload_slice(prx):
|
||||
@@ -139,7 +139,7 @@ def do_main_program():
|
||||
initdata.properties = Ice.createProperties([], initdata.properties)
|
||||
for prop, val in cfg.iceraw:
|
||||
initdata.properties.setProperty(prop, val)
|
||||
|
||||
|
||||
initdata.properties.setProperty('Ice.ImplicitContext', 'Shared')
|
||||
initdata.properties.setProperty('Ice.Default.EncodingVersion', '1.0')
|
||||
initdata.logger = CustomLogger()
|
||||
@@ -147,80 +147,81 @@ def do_main_program():
|
||||
ice = Ice.initialize(initdata)
|
||||
prxstr = 'Meta:tcp -h %s -p %d' % (cfg.ice.host, cfg.ice.port)
|
||||
prx = ice.stringToProxy(prxstr)
|
||||
|
||||
|
||||
if not cfg.ice.slice:
|
||||
dynload_slice(prx)
|
||||
else:
|
||||
fsload_slice(cfg.ice.slice)
|
||||
|
||||
|
||||
import Murmur
|
||||
|
||||
|
||||
class mumoIceApp(Ice.Application):
|
||||
def __init__(self, manager):
|
||||
Ice.Application.__init__(self)
|
||||
self.manager = manager
|
||||
|
||||
|
||||
def run(self, args):
|
||||
self.shutdownOnInterrupt()
|
||||
|
||||
|
||||
if not self.initializeIceConnection():
|
||||
return 1
|
||||
|
||||
|
||||
if cfg.ice.watchdog > 0:
|
||||
self.metaUptime = -1
|
||||
self.checkConnection()
|
||||
|
||||
|
||||
# Serve till we are stopped
|
||||
self.communicator().waitForShutdown()
|
||||
self.watchdog.cancel()
|
||||
|
||||
|
||||
if self.interrupted():
|
||||
warning('Caught interrupt, shutting down')
|
||||
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
def initializeIceConnection(self):
|
||||
"""
|
||||
Establishes the two-way Ice connection and adds MuMo to the
|
||||
configured servers
|
||||
"""
|
||||
ice = self.communicator()
|
||||
|
||||
|
||||
if cfg.ice.secret:
|
||||
debug('Using shared ice secret')
|
||||
ice.getImplicitContext().put("secret", cfg.ice.secret)
|
||||
else:
|
||||
warning('Consider using an ice secret to improve security')
|
||||
|
||||
|
||||
info('Connecting to Ice server (%s:%d)', cfg.ice.host, cfg.ice.port)
|
||||
base = ice.stringToProxy(prxstr)
|
||||
self.meta =Murmur.MetaPrx.uncheckedCast(base)
|
||||
|
||||
self.meta = Murmur.MetaPrx.uncheckedCast(base)
|
||||
|
||||
if cfg.ice.callback_port > 0:
|
||||
cbp = ' -p %d' % cfg.ice.callback_port
|
||||
else:
|
||||
cbp = ''
|
||||
|
||||
|
||||
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', 'tcp -h %s%s' % (cfg.ice.callback_host, cbp))
|
||||
adapter.activate()
|
||||
self.adapter = adapter
|
||||
|
||||
self.manager.setClientAdapter(adapter)
|
||||
|
||||
metacbprx = adapter.addWithUUID(metaCallback(self))
|
||||
self.metacb = Murmur.MetaCallbackPrx.uncheckedCast(metacbprx)
|
||||
|
||||
|
||||
return self.attachCallbacks()
|
||||
|
||||
|
||||
def attachCallbacks(self):
|
||||
"""
|
||||
Attaches all callbacks
|
||||
"""
|
||||
|
||||
|
||||
# Ice.ConnectionRefusedException
|
||||
debug('Attaching callbacks')
|
||||
try:
|
||||
info('Attaching meta callback')
|
||||
self.meta.addCallback(self.metacb)
|
||||
|
||||
|
||||
for server in self.meta.getBootedServers():
|
||||
sid = server.id()
|
||||
if not cfg.murmur.servers or sid in cfg.murmur.servers:
|
||||
@@ -228,7 +229,7 @@ def do_main_program():
|
||||
servercbprx = self.adapter.addWithUUID(serverCallback(self.manager, server, sid))
|
||||
servercb = Murmur.ServerCallbackPrx.uncheckedCast(servercbprx)
|
||||
server.addCallback(servercb)
|
||||
|
||||
|
||||
except (Murmur.InvalidSecretException, Ice.UnknownUserException, Ice.ConnectionRefusedException), e:
|
||||
if isinstance(e, Ice.ConnectionRefusedException):
|
||||
error('Server refused connection')
|
||||
@@ -238,15 +239,15 @@ def do_main_program():
|
||||
else:
|
||||
# We do not actually want to handle this one, re-raise it
|
||||
raise e
|
||||
|
||||
|
||||
self.connected = False
|
||||
self.manager.announceDisconnected()
|
||||
return False
|
||||
|
||||
|
||||
self.connected = True
|
||||
self.manager.announceConnected(self.meta)
|
||||
return True
|
||||
|
||||
|
||||
def checkConnection(self):
|
||||
"""
|
||||
Tries to retrieve the server uptime to determine wheter the server is
|
||||
@@ -255,7 +256,7 @@ def do_main_program():
|
||||
#debug('Watchdog run')
|
||||
try:
|
||||
uptime = self.meta.getUptime()
|
||||
if self.metaUptime > 0:
|
||||
if self.metaUptime > 0:
|
||||
# Check if the server didn't restart since we last checked, we assume
|
||||
# since the last time we ran this check the watchdog interval +/- 5s
|
||||
# have passed. This should be replaced by implementing a Keepalive in
|
||||
@@ -263,17 +264,17 @@ def do_main_program():
|
||||
if not ((uptime - 5) <= (self.metaUptime + cfg.ice.watchdog) <= (uptime + 5)):
|
||||
# Seems like the server restarted, re-attach the callbacks
|
||||
self.attachCallbacks()
|
||||
|
||||
|
||||
self.metaUptime = uptime
|
||||
except Ice.Exception, e:
|
||||
error('Connection to server lost, will try to reestablish callbacks in next watchdog run (%ds)', cfg.ice.watchdog)
|
||||
debug(str(e))
|
||||
self.attachCallbacks()
|
||||
|
||||
|
||||
# Renew the timer
|
||||
self.watchdog = Timer(cfg.ice.watchdog, self.checkConnection)
|
||||
self.watchdog.start()
|
||||
|
||||
|
||||
def checkSecret(func):
|
||||
"""
|
||||
Decorator that checks whether the server transmitted the right secret
|
||||
@@ -281,28 +282,28 @@ def do_main_program():
|
||||
"""
|
||||
if not cfg.ice.secret:
|
||||
return func
|
||||
|
||||
|
||||
def newfunc(*args, **kws):
|
||||
if 'current' in kws:
|
||||
current = kws["current"]
|
||||
else:
|
||||
current = args[-1]
|
||||
|
||||
|
||||
if not current or 'secret' not in current.ctx or current.ctx['secret'] != cfg.ice.secret:
|
||||
error('Server transmitted invalid secret. Possible injection attempt.')
|
||||
raise Murmur.InvalidSecretException()
|
||||
|
||||
|
||||
return func(*args, **kws)
|
||||
|
||||
|
||||
return newfunc
|
||||
|
||||
|
||||
def fortifyIceFu(retval=None, exceptions=(Ice.Exception,)):
|
||||
"""
|
||||
Decorator that catches exceptions,logs them and returns a safe retval
|
||||
value. This helps to prevent getting stuck in
|
||||
critical code paths. Only exceptions that are instances of classes
|
||||
given in the exceptions list are not caught.
|
||||
|
||||
|
||||
The default is to catch all non-Ice exceptions.
|
||||
"""
|
||||
def newdec(func):
|
||||
@@ -315,13 +316,13 @@ def do_main_program():
|
||||
if isinstance(e, ex):
|
||||
catch = False
|
||||
break
|
||||
|
||||
|
||||
if catch:
|
||||
critical('Unexpected exception caught')
|
||||
exception(e)
|
||||
return retval
|
||||
raise
|
||||
|
||||
|
||||
return newfunc
|
||||
return newdec
|
||||
|
||||
@@ -329,7 +330,7 @@ def do_main_program():
|
||||
def __init__(self, app):
|
||||
Murmur.MetaCallback.__init__(self)
|
||||
self.app = app
|
||||
|
||||
|
||||
@fortifyIceFu()
|
||||
@checkSecret
|
||||
def started(self, server, current=None):
|
||||
@@ -344,20 +345,20 @@ def do_main_program():
|
||||
servercbprx = self.app.adapter.addWithUUID(serverCallback(self.app.manager, server, sid))
|
||||
servercb = Murmur.ServerCallbackPrx.uncheckedCast(servercbprx)
|
||||
server.addCallback(servercb)
|
||||
|
||||
|
||||
# Apparently this server was restarted without us noticing
|
||||
except (Murmur.InvalidSecretException, Ice.UnknownUserException), e:
|
||||
if hasattr(e, "unknown") and e.unknown != "Murmur::InvalidSecretException":
|
||||
# Special handling for Murmur 1.2.2 servers with invalid slice files
|
||||
raise e
|
||||
|
||||
|
||||
error('Invalid ice secret')
|
||||
return
|
||||
else:
|
||||
debug('Virtual server %d got started', sid)
|
||||
|
||||
|
||||
self.app.manager.announceMeta(sid, "started", server, current)
|
||||
|
||||
|
||||
@fortifyIceFu()
|
||||
@checkSecret
|
||||
def stopped(self, server, current=None):
|
||||
@@ -378,10 +379,10 @@ def do_main_program():
|
||||
except Ice.ConnectionRefusedException:
|
||||
self.app.connected = False
|
||||
self.app.manager.announceDisconnected()
|
||||
|
||||
|
||||
debug('Server shutdown stopped a virtual server')
|
||||
|
||||
|
||||
|
||||
|
||||
def forwardServer(fu):
|
||||
def new_fu(self, *args, **kwargs):
|
||||
self.manager.announceServer(self.sid, fu.__name__, self.server, *args, **kwargs)
|
||||
@@ -393,12 +394,12 @@ def do_main_program():
|
||||
self.manager = manager
|
||||
self.sid = sid
|
||||
self.server = server
|
||||
|
||||
|
||||
# Hack to prevent every call to server.id() from the client callbacks
|
||||
# from having to go over Ice
|
||||
def id_replacement():
|
||||
return self.sid
|
||||
|
||||
|
||||
server.id = id_replacement
|
||||
|
||||
@checkSecret
|
||||
@@ -412,7 +413,7 @@ def do_main_program():
|
||||
def userConnected(self, u, current=None): pass
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def channelCreated(self, c, current=None): pass
|
||||
def channelCreated(self, c, current=None): pass
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def channelRemoved(self, c, current=None): pass
|
||||
@@ -422,56 +423,56 @@ def do_main_program():
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def userTextMessage(self, u, m, current=None) : pass
|
||||
|
||||
class contextCallback(Murmur.ServerContextCallback):
|
||||
def __init__(self, manager, server, sid):
|
||||
|
||||
class customContextCallback(Murmur.ServerContextCallback):
|
||||
def __init__(self, contextActionCallback, *ctx):
|
||||
Murmur.ServerContextCallback.__init__(self)
|
||||
self.manager = manager
|
||||
self.server = server
|
||||
self.sid = sid
|
||||
|
||||
self.cb = contextActionCallback
|
||||
self.ctx = ctx
|
||||
|
||||
@checkSecret
|
||||
def contextAction(self, action, p, session, chanid, current=None):
|
||||
self.manager.announceContext(self.sid, "contextAction", self.server, action, p, session, chanid, current)
|
||||
def contextAction(self, *args, **argv):
|
||||
# (action, user, target_session, target_chanid, current=None)
|
||||
self.cb(*(self.ctx + args), **argv)
|
||||
|
||||
#
|
||||
#--- Start of moderator
|
||||
#
|
||||
info('Starting mumble moderator')
|
||||
debug('Initializing manager')
|
||||
manager = MumoManager(Murmur)
|
||||
manager = MumoManager(Murmur, customContextCallback)
|
||||
manager.start()
|
||||
manager.loadModules()
|
||||
manager.startModules()
|
||||
|
||||
debug("Initializing mumoIceApp")
|
||||
|
||||
debug("Initializing mumoIceApp")
|
||||
app = mumoIceApp(manager)
|
||||
state = app.main(sys.argv[:1], initData=initdata)
|
||||
|
||||
|
||||
manager.stopModules()
|
||||
manager.stop()
|
||||
info('Shutdown complete')
|
||||
return state
|
||||
|
||||
|
||||
class CustomLogger(Ice.Logger):
|
||||
"""
|
||||
Logger implementation to pipe Ice log messages into
|
||||
our own log
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
Ice.Logger.__init__(self)
|
||||
self._log = getLogger('Ice')
|
||||
|
||||
|
||||
def _print(self, message):
|
||||
self._log.info(message)
|
||||
|
||||
|
||||
def trace(self, category, message):
|
||||
self._log.debug('Trace %s: %s', category, message)
|
||||
|
||||
|
||||
def warning(self, message):
|
||||
self._log.warning(message)
|
||||
|
||||
|
||||
def error(self, message):
|
||||
self._log.error(message)
|
||||
|
||||
@@ -492,11 +493,11 @@ if __name__ == '__main__':
|
||||
parser.add_option('-a', '--app', action='store_true', dest='force_app',
|
||||
help='do not run as daemon', default=False)
|
||||
(option, args) = parser.parse_args()
|
||||
|
||||
|
||||
if option.force_daemon and option.force_app:
|
||||
parser.print_help()
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Load configuration
|
||||
try:
|
||||
cfg = Config(option.ini, default)
|
||||
@@ -504,7 +505,7 @@ if __name__ == '__main__':
|
||||
print >> sys.stderr, 'Fatal error, could not load config file from "%s"' % cfgfile
|
||||
print >> sys.stderr, e
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
# Initialise logger
|
||||
if cfg.log.file:
|
||||
try:
|
||||
@@ -515,19 +516,19 @@ if __name__ == '__main__':
|
||||
sys.exit(1)
|
||||
else:
|
||||
logfile = logging.sys.stderr
|
||||
|
||||
|
||||
|
||||
|
||||
if option.verbose:
|
||||
level = cfg.log.level
|
||||
else:
|
||||
level = logging.ERROR
|
||||
|
||||
|
||||
logging.basicConfig(level=level,
|
||||
format='%(asctime)s %(levelname)s %(name)s %(message)s',
|
||||
stream=logfile)
|
||||
|
||||
|
||||
# As the default try to run as daemon. Silently degrade to running as a normal application if this fails
|
||||
# unless the user explicitly defined what he expected with the -a / -d parameter.
|
||||
# unless the user explicitly defined what he expected with the -a / -d parameter.
|
||||
try:
|
||||
if option.force_app:
|
||||
raise ImportError # Pretend that we couldn't import the daemon lib
|
||||
|
Reference in New Issue
Block a user