Added onjoin module for moving players on join as the first mumo plugin. Numerous fixes/changes in the underlying stuff. Moved sample configuration files from modules-enabled to modules-available.

This commit is contained in:
Stefan Hacker
2010-12-20 18:13:04 +01:00
parent 41e1e75211
commit a71cf75a83
10 changed files with 247 additions and 84 deletions

View File

@ -0,0 +1,20 @@
;
; This module allows moving players into a specific channel once
; they connect regardless of which channel they were in when they left.
;
[onjoin]
; Comma seperated list of servers to operate on, leave empty for all
servers =
[all]
; Id of the channel to move users into once they join.
channel = 2
; For every server you want to override the [all] section for create
; a [server_<serverid>] section. For example:
; Overriding [all] for server with the id 1 would look like this
;[server_1]
;channel = 4

View File

@ -0,0 +1,14 @@
; This file is a dummy configuration file for the
; test module. The test module has heavy debug output
; and is solely meant for testing the basic framework
; as well as debugging purposes. Usually you don't want
; to enable it.
[testing]
tvar = 1
tvar2 = Bernd
tvar3 = -1
tvar4 = True
[blub]
Bernd = asdad
asdasd = dasdw

View File

@ -1,9 +0,0 @@
[testing]
tvar = 1
tvar2 = Bernd
tvar3 = -1
tvar4 = True
[blub]
Bernd = asdad
asdasd = dasdw

93
modules/onjoin.py Normal file
View File

@ -0,0 +1,93 @@
#!/usr/bin/env python
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
# - Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# - Redistributions in binary form must reproduce the above copyright notice,
# this list of conditions and the following disclaimer in the documentation
# and/or other materials provided with the distribution.
# - Neither the name of the Mumble Developers nor the names of its
# contributors may be used to endorse or promote products derived from this
# software without specific prior written permission.
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# `AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
# CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
# EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from mumo_module import (x2bool,
commaSeperatedIntegers,
MumoModule,
Config)
import re
class onjoin(MumoModule):
default_config = {'onjoin':(
('servers', commaSeperatedIntegers, []),
),
'all':(
('channel', int, 1),
),
lambda x: re.match('server_\d+', x):(
('channel', int, 1),
)
}
def __init__(self, name, manager, configuration = None):
MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule()
def connected(self):
manager = self.manager()
log = self.log()
log.debug("Register for Server callbacks")
servers = self.cfg().onjoin.servers
if not servers:
servers = manager.SERVERS_ALL
manager.subscribeServerCallbacks(self, servers)
def disconnected(self): pass
#
#--- Server callback functions
#
def userConnected(self, server, state, context = None):
log = self.log()
sid = server.id()
try:
scfg = getattr(self.cfg(), 'server_%d' % sid)
except AttributeError:
scfg = self.cfg().all
if state.channel != scfg.channel:
log.debug("Moving user '%s' from channel %d to %d on server %d", state.name, state.channel, scfg.channel, sid)
state.channel = scfg.channel
try:
server.setState(state)
except self.murmur.InvalidChannelException:
log.error("Moving user '%s' failed, target channel %d does not exist on server %d", state.name, scfg.channel, sid)
def userDisconnected(self, server, state, context = None): pass
def userStateChanged(self, server, state, context = None): pass
def channelCreated(self, server, state, context = None): pass
def channelRemoved(self, server, state, context = None): pass
def channelStateChanged(self, server, state, context = None): pass

View File

@ -72,32 +72,32 @@ class test(MumoModule):
#--- Server callback functions
#
@logModFu
def userConnected(self, state, context = None):
def userConnected(self, server, state, context = None):
pass
@logModFu
def userDisconnected(self, state, context = None):
def userDisconnected(self, server, state, context = None):
pass
@logModFu
def userStateChanged(self, state, context = None):
def userStateChanged(self, server, state, context = None):
pass
@logModFu
def channelCreated(self, state, context = None):
def channelCreated(self, server, state, context = None):
pass
@logModFu
def channelRemoved(self, state, context = None):
def channelRemoved(self, server, state, context = None):
pass
@logModFu
def channelStateChanged(self, state, context = None):
def channelStateChanged(self, server, state, context = None):
pass
#
#--- Server context callback functions
#
@logModFu
def contextAction(self, action, user, session, channelid, context = None):
def contextAction(self, server, action, user, session, channelid, context = None):
pass

View File

@ -7,39 +7,39 @@
; Host and port of the Ice interface on
; the target Murmur server.
;host = 127.0.0.1
;port = 6502
host = 127.0.0.1
port = 6502
; If you do not define a slicefile here MuMo
; will try to automatically retrieve it from
; the server. This forces need_on_startup to
; True
; Slicefile to use
;slice =
slice = Murmur.ice
; Shared secret between the MuMo and the Murmur
; server. For security reason you should always
; use a shared secret.
;secret =
secret =
;Check Ice connection every x seconds
;watchdog = 15
watchdog = 15
[modules]
;mod_dir = modules/
;cfg_dir = modules-enabled/
;timeout = 2
mod_dir = modules/
cfg_dir = modules-enabled/
timeout = 2
[system]
;pidfile = muauth.pid
pidfile = muauth.pid
; Logging configuration
[log]
; Available loglevels: 10 = DEBUG (default) | 20 = INFO | 30 = WARNING | 40 = ERROR
;level = 10
;file = mumo.log
level =
file = mumo.log
[iceraw]
Ice.ThreadPool.Server.Size = 5

30
mumo.py
View File

@ -136,7 +136,7 @@ def do_main_program():
sid = server.id()
if not cfg.murmur.servers or sid in cfg.murmur.servers:
info('Setting callbacks for virtual server %d', sid)
servercbprx = self.adapter.addWithUUID(serverCallback(self.manager, sid))
servercbprx = self.adapter.addWithUUID(serverCallback(self.manager, server, sid))
servercb = Murmur.ServerCallbackPrx.uncheckedCast(servercbprx)
server.addCallback(servercb)
@ -159,7 +159,7 @@ def do_main_program():
return False
self.connected = True
self.manager.announceConnected()
self.manager.announceConnected(self.meta)
return True
def checkConnection(self):
@ -256,7 +256,7 @@ def do_main_program():
if not cfg.murmur.servers or sid in cfg.murmur.servers:
info('Setting authenticator for virtual server %d', server.id())
try:
servercbprx = self.app.adapter.addWithUUID(serverCallback(self.app.manager, sid))
servercbprx = self.app.adapter.addWithUUID(serverCallback(self.app.manager, server, sid))
servercb = Murmur.ServerCallbackPrx.uncheckedCast(servercbprx)
server.addCallback(servercb)
@ -301,45 +301,53 @@ def do_main_program():
def forwardServer(fu):
def new_fu(*args, **kwargs):
self = args[0]
self.manager.announceServer([self.sid], fu.__name__, *args, **kwargs)
def new_fu(self, *args, **kwargs):
self.manager.announceServer(self.sid, fu.__name__, self.server, *args, **kwargs)
return new_fu
class serverCallback(Murmur.ServerCallback):
def __init__(self, manager, sid):
def __init__(self, manager, server, sid):
Murmur.ServerCallback.__init__(self)
self.manager = manager
self.sid = sid
self.server = server
@checkSecret
@forwardServer
def userStateChanged(self, u, current=None): pass
@checkSecret
@forwardServer
def userDisconnected(self, u, current=None): pass
@checkSecret
@forwardServer
def userConnected(self, u, current=None): pass
@checkSecret
@forwardServer
def channelCreated(self, c, current=None): pass
@checkSecret
@forwardServer
def channelRemoved(self, c, current=None): pass
@checkSecret
@forwardServer
def channelStateChanged(self, c, current=None): pass
class contextCallback(Murmur.ServerContextCallback):
def __init__(self, manager, sid):
def __init__(self, manager, server, sid):
Murmur.ServerContextCallback.__init__(self)
self.manager = manager
self.server = server
self.sid = sid
@checkSecret
def contextAction(self, action, p, session, chanid, current=None):
self.manager.announceContext([self.sid], "contextAction", action, p, session, chanid, current)
self.manager.announceContext(self.sid, "contextAction", self.server, action, p, session, chanid, current)
#
#--- Start of moderator
#
info('Starting mumble moderator')
debug('Initializing manager')
manager = MumoManager()
manager = MumoManager(Murmur)
manager.start()
manager.loadModules()
manager.startModules()

View File

@ -47,7 +47,7 @@ class FailedLoadModuleImportException(FailedLoadModuleException):
class FailedLoadModuleInitializationException(FailedLoadModuleException):
pass
def debug_log(fu, enable = True):
def debug_log(enable = True):
def new_dec(fu):
def new_fu(*args, **kwargs):
self = args[0]
@ -163,6 +163,17 @@ class MumoManagerRemote(object):
"""
return self.__master.unsubscribeContextCallbacks(self.__queue, handler, servers)
def getMurmurModule(self):
"""
Returns the Murmur module generated from the slice file
"""
return self.__master.getMurmurModule()
def getMeta(self):
"""
Returns the connected servers meta module or None if it is not available
"""
return self.__master.getMeta()
class MumoManager(Worker):
@ -172,13 +183,16 @@ class MumoManager(Worker):
('cfg_dir', str, "modules-enabled/"),
('timeout', int, 2))}
def __init__(self, cfg = Config(default = cfg_default)):
def __init__(self, murmur, cfg = Config(default = cfg_default)):
Worker.__init__(self, "MumoManager")
self.queues = {} # {queue:module}
self.modules = {} # {name:module}
self.imports = {} # {name:import}
self.cfg = cfg
self.murmur = murmur
self.meta = None
self.metaCallbacks = {} # {sid:{queue:[handler]}}
self.serverCallbacks = {}
self.contextCallbacks = {}
@ -201,38 +215,37 @@ class MumoManager(Worker):
except KeyError, ValueError:
pass
def __announce_to_dict(self, mdict, servers, function, *args, **kwargs):
def __announce_to_dict(self, mdict, server, function, *args, **kwargs):
"""
Call function on handlers for specific servers in one of our handler
dictionaries.
@param mdict Dictionary to announce to
@param servers: Servers to announce to, ALL is always implied
@param function: Function the handler should call
@param args: Arguments for the function
@param kwargs: Keyword arguments for the function
"""
# Announce to all handlers registered to all events
try:
for queue, handlers in mdict[self.MAGIC_ALL].iteritems():
for handler in handlers:
self.__call_remote(queue, handler, function, args, kwargs)
except KeyError:
# No handler registered for MAGIC_ALL
pass
@param server Server to announce to, ALL is always implied
@param function Function the handler should call
@param args Arguments for the function
@param kwargs Keyword arguments for the function
"""
# Announce to all handlers of the given serverlist
if server == self.MAGIC_ALL:
servers = mdict.iterkeys()
else:
servers = [self.MAGIC_ALL, server]
for server in servers:
try:
for queue, handler in mdict[server].iteritems():
self.__call_remote(queue, handler, function, args, kwargs)
for queue, handlers in mdict[server].iteritems():
for handler in handlers:
self.__call_remote(queue, handler, function, *args, **kwargs)
except KeyError:
# No handler registered for that server
pass
def __call_remote(self, queue, handler, function, *args, **kwargs):
try:
func = getattr(handler, function) # Find out what to call on target
queue.put((None, func, args, kwargs))
except AttributeError, e:
mod = self.queues.get(queue, None)
myname = ""
@ -240,20 +253,21 @@ class MumoManager(Worker):
if mod == mymod:
myname = name
if myname:
self.log.error("Handler class registered by module '%s' does not handle function '%s'. Call failed.", myname, function)
self.log().error("Handler class registered by module '%s' does not handle function '%s'. Call failed.", myname, function)
else:
self.log().exception(e)
queue.put((None, func, args, kwargs))
#
#-- Module multiplexing functionality
#
@local_thread
def announceConnected(self):
def announceConnected(self, meta = None):
"""
Call connected handler on all handlers
"""
self.meta = meta
for queue, module in self.queues.iteritems():
self.__call_remote(queue, module, "connected")
@ -266,40 +280,40 @@ class MumoManager(Worker):
self.__call_remote(queue, module, "disconnected")
@local_thread
def announceMeta(self, servers, function, *args, **kwargs):
def announceMeta(self, server, function, *args, **kwargs):
"""
Call a function on the meta handlers
@param servers Servers to announce to
@param server Servers to announce to
@param function Name of the function to call on the handler
@param args List of arguments
@param kwargs List of keyword arguments
"""
self.__announce_to_dict(self.metaCallbacks, servers, function, *args, **kwargs)
self.__announce_to_dict(self.metaCallbacks, server, function, *args, **kwargs)
@local_thread
def announceServer(self, servers, function, *args, **kwargs):
def announceServer(self, server, function, *args, **kwargs):
"""
Call a function on the server handlers
@param servers Servers to announce to
@param server Server to announce to
@param function Name of the function to call on the handler
@param args List of arguments
@param kwargs List of keyword arguments
"""
self.__announce_to_dict(self.serverCallbacks, servers, function, *args, **kwargs)
self.__announce_to_dict(self.serverCallbacks, server, function, *args, **kwargs)
@local_thread
def announceContext(self, servers, function, *args, **kwargs):
def announceContext(self, server, function, *args, **kwargs):
"""
Call a function on the context handlers
@param servers Servers to announce to
@param server Server to announce to
@param function Name of the function to call on the handler
@param args List of arguments
@param kwargs List of keyword arguments
"""
self.__announce_to_dict(self.serverCallbacks, servers, function, *args, **kwargs)
self.__announce_to_dict(self.serverCallbacks, server, function, *args, **kwargs)
#
#--- Module self management functionality
#
@ -351,8 +365,19 @@ class MumoManager(Worker):
@see MumoManagerRemote
"""
return self.__rem_from_dict(self.contextCallbacks, queue, handler, servers)
def getMurmurModule(self):
"""
Returns the Murmur module generated from the slice file
"""
return self.murmur
def getMeta(self):
"""
Returns the connected servers meta module or None if it is not available
"""
return self.meta
#
#--- Module load/start/stop/unload functionality
#
@local_thread_blocking

View File

@ -75,11 +75,11 @@ class MumoManagerTest(unittest.TestCase):
if arg1 == "arg1" and arg2 == "arg2":
self.emeta.set()
def contextCallMe(self, arg1, arg2):
def contextCallMe(self, server, arg1, arg2):
if arg1 == "arg1" and arg2 == "arg2":
self.econtext.set()
def serverCallMe(self, arg1, arg2):
def serverCallMe(self, server, arg1, arg2):
if arg1 == "arg1" and arg2 == "arg2":
self.eserver.set()
@ -95,7 +95,7 @@ class MumoManagerTest(unittest.TestCase):
#--- Helpers for independent test env creation
#
def up(self):
man = MumoManager()
man = MumoManager(None)
man.start()
mod = man.loadModuleCls("MyModule", self.mymod, self.cfg)
@ -144,21 +144,33 @@ class MumoManagerTest(unittest.TestCase):
def testMetaCallback(self):
man, mod = self.up()
man.announceConnected()
man.announceMeta([], "metaCallMe", ["arg1"], {"arg2":"arg2"})
mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set())
man.announceMeta(man.MAGIC_ALL, "metaCallMe", "arg1", arg2 = "arg2")
mod.emeta.wait(timeout=1)
assert(mod.emeta.is_set())
man.announceDisconnected()
self.down(man, mod)
def testContextCallback(self):
man, mod = self.up()
man.announceConnected()
man.announceContext([], "contextCallMe", ["arg1"], {"arg2":"arg2"})
mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set())
man.announceContext(man.MAGIC_ALL, "contextCallMe", "server", "arg1", arg2 = "arg2")
mod.econtext.wait(timeout=1)
assert(mod.econtext.is_set())
man.announceDisconnected()
self.down(man, mod)
def testServerCallback(self):
man, mod = self.up()
man.announceConnected()
man.announceServer([], "serverCallMe", ["arg1"], {"arg2":"arg2"})
mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set())
man.announceServer(man.MAGIC_ALL, "serverCallMe", "server", "arg1", arg2 = "arg2")
mod.eserver.wait(timeout=1)
assert(mod.eserver.is_set())
man.announceDisconnected()
self.down(man, mod)

View File

@ -91,10 +91,10 @@ class MumoModule(Worker):
def logModFu(fu):
def newfu(self, *args, **kwargs):
def new_fu(self, *args, **kwargs):
log = self.log()
argss = '' if len(args)==0 else ',' + ','.join(['"%s"' % str(arg) for arg in args])
kwargss = '' if len(kwargs)==0 else ','.join('%s="%s"' % (kw, str(arg)) for kw, arg in kwargs.iteritems())
log.debug("%s(%s%s%s)", fu.__name__, str(self), argss, kwargss)
return fu(self, *args, **kwargs)
return newfu
return new_fu