This commit is contained in:
2016-06-01 21:03:15 +02:00
parent c42113d712
commit 27d0d313d2
27 changed files with 1657 additions and 1518 deletions

View File

@@ -57,7 +57,7 @@ modules-enabled folder.
## Requirements
mumo requires:
* python 2.7*
* python >=3.2
* python-zeroc-ice
* murmur >=1.2.3*

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,40 +29,42 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import ConfigParser
import configparser
import types
class Config(object):
"""
Small abstraction for config loading
"""
def __init__(self, filename = None, default = None):
def __init__(self, filename=None, default=None):
if (filename and not default) or \
(not filename and not default): return
sections = set(default.iterkeys())
sections = set(default.keys())
if filename:
cfg = ConfigParser.RawConfigParser()
cfg = configparser.RawConfigParser()
cfg.optionxform = str
with open(filename) as f:
cfg.readfp(f)
cfg.read_file(f)
sections.update(cfg.sections())
for section in sections:
if type(section) == types.FunctionType: continue
if isinstance(section, types.FunctionType):
continue
match = None
for default_section in default.iterkeys():
for default_section in default.keys():
try:
if section == default_section or \
(type(default_section) == types.FunctionType and default_section(section)):
(isinstance(default_section, types.FunctionType) and default_section(section)):
match = default_section
break
except ValueError:
continue
if match == None:
if match is None:
continue
optiondefaults = default[match]
@@ -74,7 +76,7 @@ class Config(object):
else:
try:
self.__dict__[section] = cfg.items(section)
except ConfigParser.NoSectionError:
except configparser.NoSectionError:
self.__dict__[section] = []
else:
self.__dict__[section] = Config()
@@ -84,7 +86,7 @@ class Config(object):
else:
try:
self.__dict__[section].__dict__[name] = conv(cfg.get(section, name))
except (ValueError, ConfigParser.NoSectionError, ConfigParser.NoOptionError):
except (ValueError, configparser.NoSectionError, configparser.NoOptionError):
self.__dict__[section].__dict__[name] = vdefault
def __getitem__(self, key):
@@ -93,33 +95,37 @@ class Config(object):
def __contains__(self, key):
return self.__dict__.__contains__(key)
def x2bool(s):
"""
Helper function to convert strings from the config to bool
"""
if isinstance(s, bool):
return s
elif isinstance(s, basestring):
elif isinstance(s, str):
return s.strip().lower() in ['1', 'true']
raise ValueError()
def commaSeperatedIntegers(s):
"""
Helper function to convert a string from the config
containing comma seperated integers into a list of integers
"""
return map(int, s.split(','))
return list(map(int, s.split(',')))
def commaSeperatedStrings(s):
"""
Helper function to convert a string from the config
containing comma seperated strings into a list of strings
"""
return map(str.strip, s.split(','))
return list(map(str.strip, s.split(',')))
def commaSeperatedBool(s):
"""
Helper function to convert a string from the config
containing comma seperated strings into a list of booleans
"""
return map(x2bool, s.split(','))
return list(map(x2bool, s.split(',')))

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,13 +29,15 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
from config import Config, x2bool, commaSeperatedIntegers, commaSeperatedStrings, commaSeperatedBool
from tempfile import mkstemp
import os
import re
import unittest
from tempfile import mkstemp
def create_file(content = None):
from config import Config, x2bool, commaSeperatedIntegers, commaSeperatedStrings, commaSeperatedBool
def create_file(content=None):
"""
Creates a temp file filled with 'content' and returns its path.
The file has to be manually deleted later on
@@ -43,11 +45,12 @@ def create_file(content = None):
fd, path = mkstemp()
f = os.fdopen(fd, "wb")
if content:
f.write(content)
f.write(content.encode())
f.flush()
f.close()
return path
class ConfigTest(unittest.TestCase):
cfg_content = """[world]
domination = True
@@ -63,14 +66,14 @@ value = False
value = True
"""
cfg_default = {'world':(('domination', x2bool, False),
cfg_default = {'world': (('domination', x2bool, False),
('somestr', str, "fail"),
('somenum', int, 0),
('somenumtest', int, 1),
('blubber', str, "empty"),
('serverregex', re.compile, '.*')),
(lambda x: re.match("Server_\d+",x)):(('value', x2bool, True),),
'somethingelse':(('bla', str, "test"),)}
(lambda x: re.match("Server_\d+", x)): (('value', x2bool, True),),
'somethingelse': (('bla', str, "test"),)}
def setUp(self):
pass
@@ -78,69 +81,68 @@ value = True
def tearDown(self):
pass
def testEmpty(self):
path = create_file()
try:
cfg = Config(path, self.cfg_default)
assert(cfg.world.domination == False)
assert(cfg.world.somestr == "fail")
assert(cfg.world.somenum == 0)
assert (cfg.world.domination == False)
assert (cfg.world.somestr == "fail")
assert (cfg.world.somenum == 0)
self.assertRaises(AttributeError, getattr, cfg.world, "testfallbacknum")
assert(cfg.somethingelse.bla == "test")
assert (cfg.somethingelse.bla == "test")
finally:
os.remove(path)
def testX2bool(self):
assert(x2bool(" true") == True)
assert(x2bool("false") == False)
assert(x2bool(" TrUe") == True)
assert(x2bool("FaLsE ") == False)
assert(x2bool("0 ") == False)
assert(x2bool("1") == True)
assert(x2bool(" 10") == False)
assert(x2bool("notabool") == False)
assert (x2bool(" true") == True)
assert (x2bool("false") == False)
assert (x2bool(" TrUe") == True)
assert (x2bool("FaLsE ") == False)
assert (x2bool("0 ") == False)
assert (x2bool("1") == True)
assert (x2bool(" 10") == False)
assert (x2bool("notabool") == False)
def testCommaSeperatedIntegers(self):
assert(commaSeperatedIntegers(" 1,2 , 333 ") == [1,2,333])
assert (commaSeperatedIntegers(" 1,2 , 333 ") == [1, 2, 333])
self.assertRaises(ValueError, commaSeperatedIntegers, "1,2,a")
def testCommaSeperatedStrings(self):
assert(commaSeperatedStrings("Bernd, the, bred !") == ["Bernd", "the", "bred !"])
assert (commaSeperatedStrings("Bernd, the, bred !") == ["Bernd", "the", "bred !"])
def testCommaSeperatedBool(self):
assert(commaSeperatedBool("tRue ,false, 0, 0, 1,1, test") == [True, False, False, False, True, True, False])
assert (commaSeperatedBool("tRue ,false, 0, 0, 1,1, test") == [True, False, False, False, True, True, False])
def testConfig(self):
path = create_file(self.cfg_content)
try:
try:
cfg = Config(path, self.cfg_default)
except Exception, e:
print e
assert(cfg.world.domination == True)
assert(cfg.world.somestr == "Blabla")
assert(cfg.world.somenum == 10)
except Exception as e:
print(e)
assert (cfg.world.domination == True)
assert (cfg.world.somestr == "Blabla")
assert (cfg.world.somenum == 10)
self.assertRaises(AttributeError, getattr, cfg.world, "testfallbacknum")
self.assertEqual(cfg.world.blubber, "Things %(doesnotexistsasdefault)s")
self.assertEqual(cfg.world.serverregex, re.compile("^\[[\w\d\-\(\):]{1,20}\]$"))
assert(cfg.somethingelse.bla == "test")
assert(cfg.Server_10.value == False)
assert(cfg.Server_2.value == True)
assert(cfg.Server_9.value == True)
assert (cfg.somethingelse.bla == "test")
assert (cfg.Server_10.value == False)
assert (cfg.Server_2.value == True)
assert (cfg.Server_9.value == True)
finally:
os.remove(path)
def testLoadDefault(self):
cfg = Config(default=self.cfg_default)
assert(cfg.world.domination == False)
assert(cfg.somethingelse.bla == "test")
assert(cfg.world.somenum == 0)
assert (cfg.world.domination == False)
assert (cfg.somethingelse.bla == "test")
assert (cfg.world.somenum == 0)
def testGetItem(self):
cfg = Config(default=self.cfg_default)
assert(cfg["world"]["domination"] == False)
assert("world" in cfg)
assert (cfg["world"]["domination"] == False)
assert ("world" in cfg)
def invalidaccess(c):
c["nointhisconfig"]
@@ -149,5 +151,5 @@ value = True
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
# import sys;sys.argv = ['', 'Test.testName']
unittest.main()

View File

@@ -17,7 +17,7 @@ DESC="Mumo bot for Mumble"
WORKDIR=/opt/mumo
PIDDIR=$WORKDIR
PIDFILE=$PIDDIR/mumo.pid
DAEMON=/usr/bin/python
DAEMON=/usr/bin/python3
USER=mumo
GROUP=mumo

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -35,20 +35,18 @@
# gamestate reported by Mumble positional audio plugins
#
from mumo_module import (MumoModule,
x2bool)
import json
import re
try:
import json
except ImportError: # Fallback for python < 2.6
import simplejson as json
from config import x2bool
from mumo_module import MumoModule
class bf2(MumoModule):
default_config = {'bf2':(
default_config = {'bf2': (
('gamecount', int, 1),
),
lambda x: re.match('g\d+', x):(
lambda x: re.match('g\d+', x): (
('name', str, ''),
('mumble_server', int, 1),
('ipport_filter_negate', x2bool, False),
@@ -105,7 +103,7 @@ class bf2(MumoModule):
id_to_squad_name = ["no", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth"]
def __init__(self, name, manager, configuration = None):
def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule()
@@ -127,10 +125,11 @@ class bf2(MumoModule):
manager.subscribeServerCallbacks(self, servers)
manager.subscribeMetaCallbacks(self, servers)
def disconnected(self): pass
def disconnected(self):
pass
#
#--- Module specific state handling code
# --- Module specific state handling code
#
def update_state(self, server, oldstate, newstate):
log = self.log()
@@ -175,12 +174,14 @@ class bf2(MumoModule):
nli = False
if not oli and nli:
log.debug("User '%s' (%d|%d) on server %d now linked", newstate.name, newstate.session, newstate.userid, sid)
log.debug("User '%s' (%d|%d) on server %d now linked", newstate.name, newstate.session, newstate.userid,
sid)
server.addUserToGroup(0, session, "bf2_linked")
if opi and opc:
squadname = self.id_to_squad_name[opi["squad"]]
log.debug("Removing user '%s' (%d|%d) on server %d from groups of game %s / squad %s", newstate.name, newstate.session, newstate.userid, sid, og or ogcfgname, squadname)
log.debug("Removing user '%s' (%d|%d) on server %d from groups of game %s / squad %s", newstate.name,
newstate.session, newstate.userid, sid, og or ogcfgname, squadname)
server.removeUserFromGroup(ogcfg["base"], session, "bf2_%s_game" % (og or ogcfgname))
server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_commander")
server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_squad_leader")
@@ -191,7 +192,8 @@ class bf2(MumoModule):
newstate.channel = ogcfg["left"]
if npc and npi:
log.debug("Updating user '%s' (%d|%d) on server %d in game %s: %s", newstate.name, newstate.session, newstate.userid, sid, ng or ngcfgname, str(npi))
log.debug("Updating user '%s' (%d|%d) on server %d in game %s: %s", newstate.name, newstate.session,
newstate.userid, sid, ng or ngcfgname, str(npi))
squadname = self.id_to_squad_name[npi["squad"]]
@@ -239,11 +241,12 @@ class bf2(MumoModule):
newstate.channel = ngcfg[channame]
if oli and not nli:
log.debug("User '%s' (%d|%d) on server %d no longer linked", newstate.name, newstate.session, newstate.userid, sid)
log.debug("User '%s' (%d|%d) on server %d no longer linked", newstate.name, newstate.session,
newstate.userid, sid)
server.removeUserFromGroup(0, session, "bf2_linked")
if newstate.channel >= 0 and newoldchannel != newstate.channel:
if ng == None:
if 0 <= newstate.channel != newoldchannel:
if ng is None:
log.debug("Moving '%s' leaving %s to channel %s", newstate.name, og or ogcfgname, channame)
else:
log.debug("Moving '%s' @ %s to channel %s", newstate.name, ng or ngcfgname, channame)
@@ -293,13 +296,14 @@ class bf2(MumoModule):
if splitcontext[0] == "Battlefield 2":
state.is_linked = True
if state.identity and len(splitcontext) == 1:
#LEGACY: Assume broken Ice 3.2 which doesn't transmit context after \0
splitcontext.append('{"ipport":""}') # Obviously this doesn't give full functionality but it doesn't crash either ;-)
# LEGACY: Assume broken Ice 3.2 which doesn't transmit context after \0
splitcontext.append(
'{"ipport":""}') # Obviously this doesn't give full functionality but it doesn't crash either ;-)
if state.is_linked and len(splitcontext) == 2 and state.identity:
try:
context = json.loads(splitcontext[1])
verify(context, "ipport", basestring)
verify(context, "ipport", str)
for i in range(cfg.bf2.gamecount):
# Try to find a matching game
@@ -307,7 +311,7 @@ class bf2(MumoModule):
gamecfg = getattr(cfg, gamename)
if gamecfg.mumble_server == server.id():
not_matched = (gamecfg.ipport_filter.match(context["ipport"]) == None)
not_matched = (gamecfg.ipport_filter.match(context["ipport"]) is None)
if not_matched == gamecfg.ipport_filter_negate:
break
gamename = None
@@ -319,8 +323,9 @@ class bf2(MumoModule):
context["gamename"] = gamename
state.parsedcontext = context
except (ValueError, KeyError, AttributeError), e:
log.debug("Invalid context for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid, sid, repr(e))
except (ValueError, KeyError, AttributeError) as e:
log.debug("Invalid context for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid,
sid, repr(e))
try:
identity = json.loads(state.identity)
@@ -329,48 +334,57 @@ class bf2(MumoModule):
verify(identity, "squad", int)
if identity["squad"] < 0 or identity["squad"] > 9:
raise ValueError("Invalid squad number")
verify(identity, "team", basestring)
verify(identity, "team", str)
if identity["team"] != "opfor" and identity["team"] != "blufor":
raise ValueError("Invalid team identified")
#LEGACY: Ice 3.2 cannot handle unicode strings
# LEGACY: Ice 3.2 cannot handle unicode strings
identity["team"] = str(identity["team"])
state.parsedidentity = identity
except (KeyError, ValueError), e:
log.debug("Invalid identity for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid, sid, repr(e))
except (KeyError, ValueError) as e:
log.debug("Invalid identity for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid,
sid, repr(e))
# Update state and remember it
self.update_state(server, self.sessions[sid][state.session], state)
self.sessions[sid][state.session] = state
#
#--- Server callback functions
# --- Server callback functions
#
def userDisconnected(self, server, state, context = None):
def userDisconnected(self, server, state, context=None):
try:
sid = server.id()
del self.sessions[sid][state.session]
except KeyError: pass
except KeyError:
pass
def userStateChanged(self, server, state, context = None):
def userStateChanged(self, server, state, context=None):
self.handle(server, state)
def userConnected(self, server, state, context = None):
def userConnected(self, server, state, context=None):
self.handle(server, state)
def userTextMessage(self, server, user, message, current=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
def userTextMessage(self, server, user, message, current=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
#
#--- Meta callback functions
# --- Meta callback functions
#
def started(self, server, context = None):
def started(self, server, context=None):
self.sessions[server.id()] = {}
def stopped(self, server, context = None):
def stopped(self, server, context=None):
self.sessions[server.id()] = {}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -37,24 +37,20 @@
# once they become active again
#
from mumo_module import (commaSeperatedIntegers,
commaSeperatedBool,
commaSeperatedStrings,
MumoModule)
from threading import Timer
import re
from threading import Timer
from config import commaSeperatedIntegers, commaSeperatedBool, commaSeperatedStrings
from mumo_module import MumoModule
class idlemove(MumoModule):
default_config = {'idlemove':(
default_config = {'idlemove': (
('interval', float, 0.1),
('servers', commaSeperatedIntegers, []),
),
lambda x: re.match('(all)|(server_\d+)', x):(
('threshold', commaSeperatedIntegers, [3600]),
lambda x: re.match('(all)|(server_\d+)', x): (
['threshold', commaSeperatedIntegers, [3600]],
('mute', commaSeperatedBool, [True]),
('deafen', commaSeperatedBool, [False]),
('channel', commaSeperatedIntegers, [1]),
@@ -105,10 +101,8 @@ class idlemove(MumoModule):
servers = [meta.getServer(server) for server in cfg.idlemove.servers]
for server in servers:
if not server: continue
if server:
for user in server.getUsers().itervalues():
for user in server.getUsers().values():
self.UpdateUserAutoAway(server, user)
finally:
# Renew the timer
@@ -135,7 +129,6 @@ class idlemove(MumoModule):
return
# Remember values so we can see changes later
threshold = None
mute = user.mute
deafen = user.deaf
channel = user.channel
@@ -159,11 +152,8 @@ class idlemove(MumoModule):
log.warning("Incomplete configuration for stage %d of server %i, ignored", i, server.id())
continue
if user.idlesecs > threshold and\
user.channel not in scfg.channel_whitelist and\
(source_channel == -1 or\
user.channel == source_channel or\
user.channel == channel):
if user.idlesecs > threshold and user.channel not in scfg.channel_whitelist and (
source_channel == -1 or user.channel == source_channel or user.channel == channel):
over_threshold = True
# Update if state changes needed
@@ -171,13 +161,15 @@ class idlemove(MumoModule):
update = True
if user.mute != mute:
update = True
if channel >= 0 and user.channel != channel:
if 0 <= channel != user.channel:
update = True
if update:
index.add(user.session)
log.info('%ds > %ds: State transition for user %s (%d/%d) from mute %s -> %s / deaf %s -> %s | channel %d -> %d on server %d',
user.idlesecs, threshold, user.name, user.session, user.userid, user.mute, mute, user.deaf, deafen,
log.info(
'%ds > %ds: State transition for user %s (%d/%d) from mute %s -> %s / deaf %s -> %s | channel %d -> %d on server %d',
user.idlesecs, threshold, user.name, user.session, user.userid, user.mute, mute, user.deaf,
deafen,
user.channel, channel, server.id())
break
@@ -196,7 +188,7 @@ class idlemove(MumoModule):
server.setState(user)
#
#--- Server callback functions
# --- Server callback functions
#
def userDisconnected(self, server, state, context=None):
try:
@@ -209,23 +201,31 @@ class idlemove(MumoModule):
def userStateChanged(self, server, state, context=None):
self.UpdateUserAutoAway(server, state)
def userConnected(self, server, state, context=None): pass # Unused callbacks
def userTextMessage(self, server, user, message, current=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
def userConnected(self, server, state, context=None):
pass # Unused callbacks
def userTextMessage(self, server, user, message, current=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
#
#--- Meta callback functions
# --- Meta callback functions
#
def started(self, server, context = None):
def started(self, server, context=None):
sid = server.id()
self.affectedusers[sid] = set()
self.log().debug('Handling server %d', sid)
def stopped(self, server, context = None):
def stopped(self, server, context=None):
sid = server.id()
self.affectedusers[sid] = set()
self.log().debug('Server %d gone', sid)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -35,24 +35,25 @@
# they connect regardless of which channel they were in when they left.
#
from mumo_module import (commaSeperatedIntegers,
MumoModule)
import re
from config import commaSeperatedIntegers
from mumo_module import MumoModule
class onjoin(MumoModule):
default_config = {'onjoin':(
default_config = {'onjoin': (
('servers', commaSeperatedIntegers, []),
),
'all':(
'all': (
('channel', int, 1),
),
lambda x: re.match('server_\d+', x):(
lambda x: re.match('server_\d+', x): (
('channel', int, 1),
)
}
def __init__(self, name, manager, configuration = None):
def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule()
@@ -67,13 +68,14 @@ class onjoin(MumoModule):
manager.subscribeServerCallbacks(self, servers)
def disconnected(self): pass
def disconnected(self):
pass
#
#--- Server callback functions
# --- Server callback functions
#
def userConnected(self, server, state, context = None):
def userConnected(self, server, state, context=None):
log = self.log()
sid = server.id()
try:
@@ -82,17 +84,30 @@ class onjoin(MumoModule):
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)
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)
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 userTextMessage(self, server, user, message, current=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
def userDisconnected(self, server, state, context=None):
pass
def userStateChanged(self, server, state, context=None):
pass
def userTextMessage(self, server, user, message, current=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

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2015 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -35,17 +35,19 @@
# entries to a user's context menu.
#
from mumo_module import (commaSeperatedIntegers,
MumoModule)
import cgi
from config import commaSeperatedIntegers
from mumo_module import MumoModule
class samplecontext(MumoModule):
default_config = {'samplecontext':(
default_config = {'samplecontext': (
('servers', commaSeperatedIntegers, []),
),
}
def __init__(self, name, manager, configuration = None):
def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule()
self.action_poke_user = manager.getUniqueAction()
@@ -66,7 +68,7 @@ class samplecontext(MumoModule):
def disconnected(self): pass
#
#--- Server callback functions
# --- Server callback functions
#
def __on_poke_user(self, server, action, user, target):
@@ -86,7 +88,7 @@ class samplecontext(MumoModule):
self.log().info(user.name + " triggered removal")
self.manager().removeContextMenuEntry(server, action)
def userConnected(self, server, user, context = None):
def userConnected(self, server, user, context=None):
# Adding the entries here means if mumo starts up after users
# already connected they won't have the new entries before they
# reconnect. You can also use the "connected" callback to
@@ -123,10 +125,14 @@ class samplecontext(MumoModule):
self.murmur.ContextUser | self.murmur.ContextChannel | self.murmur.ContextServer
)
def userDisconnected(self, server, state, context = None): pass
def userStateChanged(self, server, state, context = None): pass
def userTextMessage(self, server, user, message, current=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
def userDisconnected(self, server, state, context=None): pass
def userStateChanged(self, server, state, context=None): pass
def userTextMessage(self, server, user, message, current=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

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2011 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -34,19 +34,20 @@
# This module allows asking the server for the last time it saw a specific player
#
from mumo_module import (commaSeperatedIntegers,
MumoModule)
from datetime import timedelta
from config import commaSeperatedIntegers
from mumo_module import MumoModule
class seen(MumoModule):
default_config = {'seen':(
default_config = {'seen': (
('servers', commaSeperatedIntegers, []),
('keyword', str, '!seen')
)
}
def __init__(self, name, manager, configuration = None):
def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule()
self.keyword = self.cfg().seen.keyword
@@ -62,7 +63,8 @@ class seen(MumoModule):
manager.subscribeServerCallbacks(self, servers)
def disconnected(self): pass
def disconnected(self):
pass
def sendMessage(self, server, user, message, msg):
if message.channels:
@@ -70,8 +72,9 @@ class seen(MumoModule):
else:
server.sendMessage(user.session, msg)
server.sendMessage(message.sessions[0], msg)
#
#--- Server callback functions
# --- Server callback functions
#
def userTextMessage(self, server, user, message, current=None):
@@ -91,7 +94,7 @@ class seen(MumoModule):
return
# Check online users
for cuser in server.getUsers().itervalues():
for cuser in server.getUsers().values():
if tuname == cuser.name:
msg = "User '%s' is currently online, has been idle for %s" % (tuname,
timedelta(seconds=cuser.idlesecs))
@@ -99,7 +102,7 @@ class seen(MumoModule):
return
# Check registrations
for cuid, cuname in server.getRegisteredUsers(tuname).iteritems():
for cuid, cuname in server.getRegisteredUsers(tuname).items():
if cuname == tuname:
ureg = server.getRegistration(cuid)
if ureg:
@@ -112,12 +115,20 @@ class seen(MumoModule):
msg = "I don't know who user '%s' is" % tuname
self.sendMessage(server, user, message, msg)
def userConnected(self, server, state, context=None):
pass
def userDisconnected(self, server, state, context=None):
pass
def userConnected(self, server, state, context = None): pass
def userDisconnected(self, server, state, context = None): pass
def userStateChanged(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
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

@@ -1 +1 @@
from source import source
from .source import source

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -31,13 +31,14 @@
import sqlite3
#TODO: Functions returning channels probably should return a dict instead of a tuple
# TODO: Functions returning channels probably should return a dict instead of a tuple
class SourceDB(object):
NO_SERVER = ""
NO_TEAM = -1
def __init__(self, path = ":memory:"):
def __init__(self, path=":memory:"):
"""
Initialize the sqlite database in the given path. If no path
is given the database is created in memory.
@@ -80,33 +81,34 @@ class SourceDB(object):
"""
True if the database is correctly initialized
"""
return self.db != None
return self.db is not None
def nameFor(self, sid, game, server = NO_SERVER, team = NO_TEAM, default = ""):
def nameFor(self, sid, game, server=NO_SERVER, team=NO_TEAM, default=""):
"""
Returns the mapped name for the given parameters or default if no
mapping exists.
"""
assert(sid != None and game != None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
assert (sid is not None and game is not None)
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
v = self.db.execute("SELECT name FROM mapped_names WHERE sid is ? and game is ? and server is ? and team is ?", [sid, game, server, team]).fetchone()
v = self.db.execute("SELECT name FROM mapped_names WHERE sid is ? and game is ? and server is ? and team is ?",
[sid, game, server, team]).fetchone()
return v[0] if v else default
def mapName(self, name, sid, game, server = NO_SERVER, team = NO_TEAM):
def mapName(self, name, sid, game, server=NO_SERVER, team=NO_TEAM):
"""
Stores a mapping for the given (sid, game, server, team) combination
to the given name. The mapping can then be retrieved with nameFor() in
the future.
"""
assert(sid != None and game != None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
assert (sid is not None and game is not None)
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
self.db.execute("INSERT OR REPLACE into mapped_names (sid, game, server, team, name) VALUES (?,?,?,?,?)",[sid, game, server, team, name])
self.db.execute("INSERT OR REPLACE into mapped_names (sid, game, server, team, name) VALUES (?,?,?,?,?)",
[sid, game, server, team, name])
self.db.commit()
def cidFor(self, sid, game, server = NO_SERVER, team = NO_TEAM):
def cidFor(self, sid, game, server=NO_SERVER, team=NO_TEAM):
"""
Returns the channel id for game specific channel. If only game
is passed the game root channel cid is returned. If additionally
@@ -115,10 +117,12 @@ class SourceDB(object):
If no channel matching the arguments has been registered with the database
before None is returned.
"""
assert(sid != None and game != None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
assert (sid is not None and game is not None)
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
v = self.db.execute("SELECT cid FROM controlled_channels WHERE sid is ? and game is ? and server is ? and team is ?", [sid, game, server, team]).fetchone()
v = self.db.execute(
"SELECT cid FROM controlled_channels WHERE sid is ? and game is ? and server is ? and team is ?",
[sid, game, server, team]).fetchone()
return v[0] if v else None
def channelForCid(self, sid, cid):
@@ -126,41 +130,48 @@ class SourceDB(object):
Returns a tuple of (sid, cid, game, server, team) for the given cid.
Returns None if the cid is unknown.
"""
assert(sid != None and cid != None)
return self.db.execute("SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and cid is ?", [sid, cid]).fetchone()
assert (sid is not None and cid is not None)
return self.db.execute(
"SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and cid is ?",
[sid, cid]).fetchone()
def channelFor(self, sid, game, server = NO_SERVER, team = NO_TEAM):
def channelFor(self, sid, game, server=NO_SERVER, team=NO_TEAM):
"""
Returns matching channel as (sid, cid, game, server, team) tuple. Matching
behavior is the same as for cidFor()
"""
assert(sid != None and game != None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
assert (sid is not None and game is not None)
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
v = self.db.execute("SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and game is ? and server is ? and team is ?", [sid, game, server, team]).fetchone()
v = self.db.execute(
"SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and game is ? and server is ? and team is ?",
[sid, game, server, team]).fetchone()
return v
def channelsFor(self, sid, game, server = NO_SERVER, team = NO_TEAM):
def channelsFor(self, sid, game, server=NO_SERVER, team=NO_TEAM):
"""
Returns matching channels as a list of (sid, cid, game, server, team) tuples.
If only the game is passed all server and team channels are matched.
This can be limited by passing server (and team).
Returns empty list if no matches are found.
"""
assert(sid != None and game != None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
assert (sid is not None and game is not None)
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
suffix, params = self.__whereClauseForOptionals(server, team)
return self.db.execute("SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and game is ?" + suffix, [sid, game] + params).fetchall()
return self.db.execute(
"SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and game is ?" + suffix,
[sid, game] + params).fetchall()
def registerChannel(self, sid, cid, game, server = NO_SERVER, team = NO_TEAM):
def registerChannel(self, sid, cid, game, server=NO_SERVER, team=NO_TEAM):
"""
Register a given channel with the database.
"""
assert(sid != None and game != None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
assert (sid is not None and game is not None)
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
self.db.execute("INSERT INTO controlled_channels (sid, cid, game, server, team) VALUES (?,?,?,?,?)", [sid, cid, game, server, team])
self.db.execute("INSERT INTO controlled_channels (sid, cid, game, server, team) VALUES (?,?,?,?,?)",
[sid, cid, game, server, team])
self.db.commit()
return True
@@ -173,18 +184,18 @@ class SourceDB(object):
"""
if server != self.NO_SERVER and team != self.NO_TEAM:
return (" and server is ? and team is ?", [server, team])
return " and server is ? and team is ?", [server, team]
elif server != self.NO_SERVER:
return (" and server is ?", [server])
return " and server is ?", [server]
else:
return ("", [])
return "", []
def unregisterChannel(self, sid, game, server = NO_SERVER, team = NO_TEAM):
def unregisterChannel(self, sid, game, server=NO_SERVER, team=NO_TEAM):
"""
Unregister a channel previously registered with the database.
"""
assert(sid != None and game != None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
assert (sid is not None and game is not None)
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
suffix, params = self.__whereClauseForOptionals(server, team)
self.db.execute("DELETE FROM controlled_channels WHERE sid is ? and game is ?" + suffix, [sid, game] + params)
@@ -194,7 +205,7 @@ class SourceDB(object):
"""
Drops channel with given sid + cid
"""
assert(sid != None and cid != None)
assert (sid is not None and cid is not None)
self.db.execute("DELETE FROM controlled_channels WHERE sid is ? and cid is ?", [sid, cid])
self.db.commit()
@@ -203,10 +214,10 @@ class SourceDB(object):
"""
Returns true if a channel with given sid and cid is registered
"""
assert(sid != None and cid != None)
assert (sid is not None and cid is not None)
res = self.db.execute("SELECT cid FROM controlled_channels WHERE sid is ? and cid is ?", [sid, cid]).fetchone()
return res != None
return res is not None
def registeredChannels(self):
"""
@@ -222,5 +233,6 @@ class SourceDB(object):
self.db.execute("DELETE FROM controlled_channels")
self.db.commit()
if __name__ == "__main__":
pass

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,9 +29,11 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
from db import SourceDB
import sqlite3
import unittest
from .db import SourceDB
class SourceDBTest(unittest.TestCase):
def setUp(self):
@@ -40,7 +42,6 @@ class SourceDBTest(unittest.TestCase):
def tearDown(self):
self.db.close()
def testOk(self):
self.db.reset()
@@ -49,7 +50,11 @@ class SourceDBTest(unittest.TestCase):
def testSingleChannel(self):
self.db.reset()
sid = 5; cid = 10; game = "tf2"; server = "abc[]def"; team = "1"
sid = 5
cid = 10
game = "tf2"
server = "abc[]def"
team = "1"
self.assertTrue(self.db.registerChannel(sid, cid, game, server, team))
self.assertEqual(self.db.cidFor(sid, game, server, team), cid)
self.db.unregisterChannel(sid, game, server, team)
@@ -58,8 +63,13 @@ class SourceDBTest(unittest.TestCase):
def testChannelTree(self):
self.db.reset()
sid = 5; game = "tf2"; server = "abc[]def"; team = 0
bcid = 10; scid = 11; tcid = 12
sid = 5
game = "tf2"
server = "abc[]def"
team = 0
bcid = 10
scid = 11
tcid = 12
self.assertTrue(self.db.registerChannel(sid, 1, "canary", server, team))
@@ -72,7 +82,7 @@ class SourceDBTest(unittest.TestCase):
self.assertEqual(self.db.cidFor(sid, game), bcid)
self.assertEqual(self.db.cidFor(sid, game, server), scid)
self.assertEqual(self.db.cidFor(sid, game, server, team), tcid)
self.assertEqual(self.db.cidFor(sid+1, game, server, team), None)
self.assertEqual(self.db.cidFor(sid + 1, game, server, team), None)
self.db.unregisterChannel(sid, game)
@@ -115,7 +125,9 @@ class SourceDBTest(unittest.TestCase):
def testDropChannel(self):
self.db.reset()
sid = 1; cid = 5; game = "tf"
sid = 1
cid = 5
game = "tf"
self.db.registerChannel(sid, cid, game)
self.db.dropChannel(sid + 1, cid)
self.assertEqual(self.db.cidFor(sid, game), cid)
@@ -125,30 +137,36 @@ class SourceDBTest(unittest.TestCase):
def testRegisteredChannels(self):
self.db.reset()
sid = 5; game = "tf2"; server = "abc[]def"; team = 1
bcid = 10; scid = 11; tcid = 12;
sid = 5
game = "tf2"
server = "abc[]def"
team = 1
bcid = 10
scid = 11
tcid = 12
self.db.registerChannel(sid, bcid, game)
self.db.registerChannel(sid, scid, game, server)
self.db.registerChannel(sid+1, tcid, game, server, team)
self.db.registerChannel(sid + 1, tcid, game, server, team)
self.db.registerChannel(sid, tcid, game, server, team)
expected = [(sid, bcid, game, self.db.NO_SERVER, self.db.NO_TEAM),
(sid, scid, game, server, self.db.NO_TEAM),
(sid, tcid, game, server, team),
(sid+1, tcid, game, server, team)]
(sid + 1, tcid, game, server, team)]
self.assertEqual(self.db.registeredChannels(), expected)
def testIsRegisteredChannel(self):
self.db.reset()
sid = 1; cid = 0; game = "tf"
sid = 1
cid = 0
game = "tf"
self.db.registerChannel(sid, cid, game)
self.assertTrue(self.db.isRegisteredChannel(sid, cid))
self.assertFalse(self.db.isRegisteredChannel(sid+1, cid))
self.assertFalse(self.db.isRegisteredChannel(sid, cid+1))
self.assertFalse(self.db.isRegisteredChannel(sid + 1, cid))
self.assertFalse(self.db.isRegisteredChannel(sid, cid + 1))
self.db.unregisterChannel(sid, game)
@@ -156,10 +174,14 @@ class SourceDBTest(unittest.TestCase):
def testChannelFor(self):
self.db.reset()
sid = 1; cid = 0; game = "tf"; server = "serv"; team = 0
sid = 1
cid = 0
game = "tf"
server = "serv"
team = 0
self.db.registerChannel(sid, cid, game)
self.db.registerChannel(sid, cid+1, game, server)
self.db.registerChannel(sid, cid+2, game, server, team)
self.db.registerChannel(sid, cid + 1, game, server)
self.db.registerChannel(sid, cid + 2, game, server, team)
res = self.db.channelFor(sid, game, server, team)
self.assertEqual(res, (sid, cid + 2, game, server, team))
@@ -170,75 +192,85 @@ class SourceDBTest(unittest.TestCase):
res = self.db.channelFor(sid, game)
self.assertEqual(res, (sid, cid, game, self.db.NO_SERVER, self.db.NO_TEAM))
res = self.db.channelFor(sid, game, server, team+5)
res = self.db.channelFor(sid, game, server, team + 5)
self.assertEqual(res, None)
def testChannelForCid(self):
self.db.reset()
sid = 1; cid = 0; game = "tf"; server = "serv"; team = 0
sid = 1
cid = 0
game = "tf"
server = "serv"
team = 0
self.db.registerChannel(sid, cid, game)
self.db.registerChannel(sid, cid+1, game, server)
self.db.registerChannel(sid, cid+2, game, server, team)
self.db.registerChannel(sid, cid + 1, game, server)
self.db.registerChannel(sid, cid + 2, game, server, team)
res = self.db.channelForCid(sid, cid)
self.assertEqual(res, (sid, cid, game, self.db.NO_SERVER, self.db.NO_TEAM))
res = self.db.channelForCid(sid, cid + 1)
self.assertEqual(res, (sid, cid + 1, game, server, self.db.NO_TEAM))
res = self.db.channelForCid(sid, cid + 2)
self.assertEqual(res, (sid, cid + 2, game, server, team))
res = self.db.channelForCid(sid, cid + 3)
self.assertEqual(res, None)
def testChannelsFor(self):
self.db.reset()
sid = 1; cid = 0; game = "tf"; server = "serv"; team = 0
sid = 1
cid = 0
game = "tf"
server = "serv"
team = 0
self.db.registerChannel(sid, cid, game)
self.db.registerChannel(sid, cid+1, game, server)
self.db.registerChannel(sid, cid+2, game, server, team)
self.db.registerChannel(sid, cid + 1, game, server)
self.db.registerChannel(sid, cid + 2, game, server, team)
chans = ((sid, cid+2, game, server, team),
(sid, cid+1, game, server, self.db.NO_TEAM),
chans = ((sid, cid + 2, game, server, team),
(sid, cid + 1, game, server, self.db.NO_TEAM),
(sid, cid, game, self.db.NO_SERVER, self.db.NO_TEAM))
res = self.db.channelsFor(sid, game, server, team)
self.assertItemsEqual(res, chans[0:1])
self.assertCountEqual(res, chans[0:1])
res = self.db.channelsFor(sid, game, server)
self.assertItemsEqual(res, chans[0:2])
self.assertCountEqual(res, chans[0:2])
res = self.db.channelsFor(sid, game)
self.assertItemsEqual(res, chans)
self.assertCountEqual(res, chans)
res = self.db.channelsFor(sid+1, game)
self.assertItemsEqual(res, [])
res = self.db.channelsFor(sid + 1, game)
self.assertCountEqual(res, [])
def testChannelTableConstraints(self):
self.db.reset()
# cid constraint
sid = 1; cid = 0; game = "tf"; server = "serv"; team = 0
sid = 1
cid = 0
game = "tf"
server = "serv"
team = 0
self.db.registerChannel(sid, cid, game)
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid, "cstrike")
# combination constraint
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid+1000, game)
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid + 1000, game)
self.db.registerChannel(sid, cid+1, game, server)
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid+100, game, server)
self.db.registerChannel(sid, cid + 1, game, server)
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid + 100, game, server)
self.db.registerChannel(sid, cid+2, game, server, team)
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid+200, game, server, team)
self.db.registerChannel(sid, cid + 2, game, server, team)
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid + 200, game, server, team)
def testChannelNameMappingTableConstraints(self):
self.db.reset()
sid = 1; game = "tf"
sid = 1
game = "tf"
# mapName performs an INSERT OR REPLACE which relies on the UNIQUE constraint
self.db.mapName("SomeTestName", sid, game)
@@ -248,8 +280,11 @@ class SourceDBTest(unittest.TestCase):
def testNameMapping(self):
self.db.reset()
sid = 1; game = "tf"; server = "[12313]";team = 2
self.assertEqual(self.db.nameFor(sid, game, default = "test"), "test")
sid = 1
game = "tf"
server = "[12313]"
team = 2
self.assertEqual(self.db.nameFor(sid, game, default="test"), "test")
self.db.mapName("Game", sid, game)
self.db.mapName("Game Server", sid, game, server)
@@ -261,6 +296,7 @@ class SourceDBTest(unittest.TestCase):
self.assertEqual(self.db.nameFor(sid, game, server), "Game Server")
self.assertEqual(self.db.nameFor(sid, game, server, team), "Game Server Team")
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
# import sys;sys.argv = ['', 'Test.testName']
unittest.main()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -35,16 +35,15 @@
# gamestate reported by Mumble positional audio plugins
#
from mumo_module import (MumoModule,
commaSeperatedIntegers,
commaSeperatedStrings,
x2bool)
from db import SourceDB
from users import (User, UserRegistry)
import re
from config import commaSeperatedStrings, x2bool, commaSeperatedIntegers
from mumo_module import MumoModule
from .db import SourceDB
from .users import (User, UserRegistry)
# noinspection PyPep8Naming
class source(MumoModule):
"""
This class combines the basic mumble moderator callbacks with
@@ -60,7 +59,7 @@ class source(MumoModule):
('deleteifunused', x2bool, True)
)
default_config = {'source':(
default_config = {'source': (
('database', str, "source.sqlite"),
('basechannelid', int, 0),
('mumbleservers', commaSeperatedIntegers, []),
@@ -112,7 +111,6 @@ class source(MumoModule):
manager.subscribeServerCallbacks(self, servers)
manager.subscribeMetaCallbacks(self, servers)
def validateChannelDB(self):
"""
Makes sure the plugins internal datatbase
@@ -132,7 +130,7 @@ class source(MumoModule):
try:
state = current_mumble_server.getChannelState(cid)
self.db.mapName(state.name, sid, game, server, team)
#TODO: Verify ACL?
# TODO: Verify ACL?
except self.murmur.InvalidChannelException:
# Channel no longer exists
@@ -140,11 +138,12 @@ class source(MumoModule):
self.db.dropChannel(sid, cid)
except AttributeError:
# Server no longer exists
assert(current_mumble_server == None)
assert (current_mumble_server is None)
log.debug("(%d) Server for channel %d no longer exists. Dropped.", sid, cid)
self.db.dropChannel(sid, cid)
def disconnected(self): pass
def disconnected(self):
pass
def removeFromGroups(self, mumble_server, session, game, server, team):
"""
@@ -170,7 +169,7 @@ class source(MumoModule):
sid = mumble_server.id()
prefix = self.cfg().source.groupprefix
game_cid = self.db.cidFor(sid, game)
assert(game_cid != None)
assert (game_cid is not None)
group = prefix + game
mumble_server.addUserToGroup(game_cid, session, group) # Game
@@ -181,17 +180,17 @@ class source(MumoModule):
group += "_" + str(team)
mumble_server.addUserToGroup(game_cid, session, group) # Team
def transitionPresentUser(self, mumble_server, old, new, sid, user_new):
"""
Transitions a user that has been and is currently playing
"""
assert(new)
assert new
target_cid = self.getOrCreateTargetChannelFor(mumble_server, new)
if user_new:
self.dlog(sid, new.state, "User started playing: g/s/t %s/%s/%d", new.game, new.server, new.identity["team"])
self.dlog(sid, new.state, "User started playing: g/s/t %s/%s/%d", new.game, new.server,
new.identity["team"])
self.addToGroups(mumble_server, new.state.session, new.game, new.server, new.identity["team"])
else:
assert old
@@ -201,12 +200,11 @@ class source(MumoModule):
return self.moveUser(mumble_server, new, target_cid)
def transitionGoneUser(self, mumble_server, old, new, sid):
"""
Transitions a user that played but is no longer doing so now.
"""
assert(old)
assert old
self.users.remove(sid, old.state.session)
@@ -220,7 +218,6 @@ class source(MumoModule):
self.dlog(sid, old.state, "User gone")
return True
def userLeftChannel(self, mumble_server, old, sid):
"""
User left channel. Make sure we check for vacancy it if the game it
@@ -247,7 +244,7 @@ class source(MumoModule):
"""
sid = mumble_server.id()
assert(not old or old.valid())
assert (not old or old.valid())
relevant = old or (new and new.valid())
if not relevant:
@@ -262,7 +259,6 @@ class source(MumoModule):
else:
moved = self.transitionGoneUser(mumble_server, old, new, sid)
if moved and old:
self.userLeftChannel(mumble_server, old, sid)
@@ -301,19 +297,18 @@ class source(MumoModule):
groupname = '~' + self.cfg().source.groupprefix + game
mumble_server.setACL(game_cid,
[ACL(applyHere = True, # Deny everything
applySubs = True,
userid = -1,
group = 'all',
deny = EAT | W | S),
ACL(applyHere = True, # Allow enter and traverse to players
applySubs = False,
userid = -1,
group = groupname,
allow = EAT)],
[ACL(applyHere=True, # Deny everything
applySubs=True,
userid=-1,
group='all',
deny=EAT | W | S),
ACL(applyHere=True, # Allow enter and traverse to players
applySubs=False,
userid=-1,
group=groupname,
allow=EAT)],
[], True)
def setACLsForServerChannel(self, mumble_server, server_cid, game, server):
"""
Sets the appropriate ACLs for a server channel for the given cid.
@@ -327,14 +322,13 @@ class source(MumoModule):
groupname = '~' + self.cfg().source.groupprefix + game + "_" + server
mumble_server.setACL(server_cid,
[ACL(applyHere = True, # Allow enter and traverse to players
applySubs = False,
userid = -1,
group = groupname,
allow = EAT)],
[ACL(applyHere=True, # Allow enter and traverse to players
applySubs=False,
userid=-1,
group=groupname,
allow=EAT)],
[], True)
def setACLsForTeamChannel(self, mumble_server, team_cid, game, server, team):
"""
Sets the appropriate ACLs for a team channel for the given cid.
@@ -348,11 +342,11 @@ class source(MumoModule):
groupname = '~' + self.cfg().source.groupprefix + game + "_" + server + "_" + str(team)
mumble_server.setACL(team_cid,
[ACL(applyHere = True, # Allow enter and traverse to players
applySubs = False,
userid = -1,
group = groupname,
allow = EAT | W | S)],
[ACL(applyHere=True, # Allow enter and traverse to players
applySubs=False,
userid=-1,
group=groupname,
allow=EAT | W | S)],
[], True)
def getOrCreateGameChannelFor(self, mumble_server, game, server, sid, cfg, log, namevars):
@@ -362,9 +356,9 @@ class source(MumoModule):
"""
sid = mumble_server.id()
game_cid = self.db.cidFor(sid, game)
if game_cid == None:
if game_cid is None:
game_channel_name = self.db.nameFor(sid, game,
default = (self.getGameName(game) % namevars))
default=(self.getGameName(game) % namevars))
log.debug("(%d) Creating game channel '%s' below %d", sid, game_channel_name, cfg.source.basechannelid)
game_cid = mumble_server.addChannel(game_channel_name, cfg.source.basechannelid)
@@ -378,7 +372,6 @@ class source(MumoModule):
log.debug("(%d) Game channel created and registered (cid %d)", sid, game_cid)
return game_cid
def getOrCreateServerChannelFor(self, mumble_server, game, server, team, sid, log, namevars, game_cid):
"""
Helper function for getting or creating only the server channel. The game
@@ -386,9 +379,9 @@ class source(MumoModule):
server channel.
"""
server_cid = self.db.cidFor(sid, game, server)
if server_cid == None:
if server_cid is None:
server_channel_name = self.db.nameFor(sid, game, server,
default = self.getServerName(game) % namevars)
default=self.getServerName(game) % namevars)
log.debug("(%d) Creating server channel '%s' below %d", sid, server_channel_name, game_cid)
server_cid = mumble_server.addChannel(server_channel_name, game_cid)
@@ -402,7 +395,6 @@ class source(MumoModule):
log.debug("(%d) Server channel created and registered (cid %d)", sid, server_cid)
return server_cid
def getOrCreateTeamChannelFor(self, mumble_server, game, server, team, sid, log, server_cid):
"""
Helper function for getting or creating only the team channel. Game and
@@ -411,9 +403,9 @@ class source(MumoModule):
"""
team_cid = self.db.cidFor(sid, game, server, team)
if team_cid == None:
if team_cid is None:
team_channel_name = self.db.nameFor(sid, game, server, team,
default = self.getTeamName(game, team))
default=self.getTeamName(game, team))
log.debug("(%d) Creating team channel '%s' below %d", sid, team_channel_name, server_cid)
team_cid = mumble_server.addChannel(team_channel_name, server_cid)
@@ -436,8 +428,8 @@ class source(MumoModule):
cfg = self.cfg()
log = self.log()
namevars = {'game' : game,
'server' : server}
namevars = {'game': game,
'server': server}
game_cid = self.getOrCreateGameChannelFor(mumble_server, game, server, sid, cfg, log, namevars)
server_cid = self.getOrCreateServerChannelFor(mumble_server, game, server, team, sid, log, namevars, game_cid)
@@ -463,7 +455,7 @@ class source(MumoModule):
user.server,
user.identity["team"])
def moveUser(self, mumble_server, user, target_cid = None):
def moveUser(self, mumble_server, user, target_cid=None):
"""
Move user according to current game state.
@@ -480,7 +472,7 @@ class source(MumoModule):
source_cid = state.channel
if target_cid == None:
if target_cid is None:
target_cid = self.getOrCreateChannelFor(mumble_server, game, server, team)
if source_cid != target_cid:
@@ -509,7 +501,7 @@ class source(MumoModule):
return False
_, _, cur_game, cur_server, cur_team = result
assert(cur_game)
assert cur_game
if not cur_server:
# Don't handle game channels
@@ -527,7 +519,7 @@ class source(MumoModule):
log.debug("(%d) Delete if unused: Channel %d in use", sid, cur_cid)
return False # Used
assert(server_channel_cid != None)
assert (server_channel_cid is not None)
# Unused. Delete server and children
log.debug("(%s) Channel %d unused. Will be deleted.", sid, server_channel_cid)
@@ -535,10 +527,10 @@ class source(MumoModule):
return True
def isValidGameType(self, game):
return self.cfg().source.gameregex.match(game) != None
return self.cfg().source.gameregex.match(game) is not None
def isValidServer(self, game, server):
return self.getGameConfig(game, "serverregex").match(server) != None
return self.getGameConfig(game, "serverregex").match(server) is not None
def parseSourceContext(self, context):
"""
@@ -552,15 +544,15 @@ class source(MumoModule):
if source != "Source engine":
# Not a source engine context
return (None, None)
return None, None
if not self.isValidGameType(game) or not self.isValidServer(game, server):
return (None, None)
return None, None
return (game, server)
return game, server
except (AttributeError, ValueError),e:
return (None, None);
except (AttributeError, ValueError) as e:
return None, None
def parseSourceIdentity(self, identity):
"""
@@ -576,7 +568,8 @@ class source(MumoModule):
d[k] = int(v)
# Make sure mandatory values are present
if not "team" in d: return None
if "team" not in d:
return None
return d
except (AttributeError, ValueError):
@@ -630,7 +623,7 @@ class source(MumoModule):
self.dlog(sid, new_state, "Transition complete")
#
#--- Server callback functions
# --- Server callback functions
#
def userDisconnected(self, server, state, context=None):
@@ -681,12 +674,14 @@ class source(MumoModule):
self.db.mapName(name, sid, game, server, team)
self.log().debug("(%d) Name mapping for channel %d updated to '%s'", sid, cid, name)
def userTextMessage(self, server, user, message, current=None): pass
def channelCreated(self, server, state, context=None): pass
def userTextMessage(self, server, user, message, current=None):
pass
def channelCreated(self, server, state, context=None):
pass
#
#--- Meta callback functions
# --- Meta callback functions
#
def started(self, server, context=None):

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,22 +29,25 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
import Queue
import config
import queue
import re
import logging
import source
import unittest
import config
from . import source
class InvalidChannelExceptionMock(Exception):
pass
class StateMock():
def __init__(self, cid = 0, session = 0, userid = -1):
def __init__(self, cid=0, session=0, userid=-1):
self.channel = cid
self.session = session
self.userid = userid
class ChannelStateMock():
def __init__(self, cid, name, parent, groups, acls):
self.id = cid
@@ -53,6 +56,7 @@ class ChannelStateMock():
self.groups = groups
self.acls = acls
class ServerMock():
def __init__(self, sid):
self.sid = sid
@@ -66,7 +70,7 @@ class ServerMock():
def addChannel(self, name, parent):
self.uid += 1
assert(not self.uid in self.channels)
assert (not self.uid in self.channels)
self.channels[self.uid] = ChannelStateMock(self.uid,
name,
parent,
@@ -80,10 +84,10 @@ class ServerMock():
if session in c.groups:
c.groups[session].add(group)
else:
c.groups[session] = set([group])
c.groups[session] = {group}
def _getChan(self, cid):
if not cid in self.channels:
if cid not in self.channels:
raise InvalidChannelExceptionMock()
return self.channels[cid]
@@ -103,8 +107,9 @@ class ServerMock():
self.channels = {} # See addChannel
self.user_state = []
class ACLMock(object):
def __init__(self, applyHere, applySubs, userid, group, deny = 0, allow = 0):
def __init__(self, applyHere, applySubs, userid, group, deny=0, allow=0):
self.applyHere = applyHere
self.applySubs = applySubs
self.userid = userid
@@ -134,31 +139,33 @@ class MockACLHelper(object):
S = MurmurMock.PermissionSpeak
EAT = E | T
ALL = E|T|W|S
ALL = E | T | W | S
ACLS = MockACLHelper
class MetaMock():
def __init__(self):
#TODO: Create range of server (or even cretae them on demand)
self.servers = {1:ServerMock(1),
5:ServerMock(5),
10:ServerMock(10)}
# TODO: Create range of server (or even cretae them on demand)
self.servers = {1: ServerMock(1),
5: ServerMock(5),
10: ServerMock(10)}
self.s = self.servers[1] # Shorthand
def getServer(self, sid):
return self.servers.get(sid, None)
def _reset(self):
for server in self.servers.itervalues():
for server in self.servers.values():
server._reset()
class ManagerMock():
SERVERS_ALL = [-1]
def __init__(self):
self.q = Queue.Queue()
self.q = queue.Queue()
self.m = MurmurMock()
self.meta = MetaMock()
@@ -172,10 +179,11 @@ class ManagerMock():
return self.meta
def subscribeServerCallbacks(self, callback, servers):
self.serverCB = {'callback' : callback, 'servers' : servers}
self.serverCB = {'callback': callback, 'servers': servers}
def subscribeMetaCallbacks(self, callback, servers):
self.metaCB = {'callback' : callback, 'servers' : servers}
self.metaCB = {'callback': callback, 'servers': servers}
class Test(unittest.TestCase):
@@ -183,7 +191,6 @@ class Test(unittest.TestCase):
self.mm = ManagerMock();
self.mserv = self.mm.meta.getServer(1)
testconfig = config.Config(None, source.source.default_config)
testconfig.source.database = ":memory:"
@@ -244,11 +251,11 @@ class Test(unittest.TestCase):
def testIdentityParser(self):
self.resetState()
expected = {"universe" : 1,
"account_type" : 2,
"id" : 3,
"instance" : 4,
"team" : 5}
expected = {"universe": 1,
"account_type": 2,
"id": 3,
"instance": 4,
"team": 5}
got = self.s.parseSourceIdentity("universe:1;account_type:2;id:00000003;instance:4;team:5")
self.assertDictEqual(expected, got)
@@ -300,7 +307,7 @@ class Test(unittest.TestCase):
i = 0
for thing in things:
acl = acls[i]
for attr, val in thing.iteritems():
for attr, val in thing.items():
self.assertEqual(getattr(acl, attr), val)
i += 1
@@ -308,7 +315,9 @@ class Test(unittest.TestCase):
mumble_server = self.mserv
prev = mumble_server._lastChannelID()
game = "tf"; server = "[A-1:123]"; team = 3
game = "tf"
server = "[A-1:123]"
team = 3
cid = self.s.getOrCreateChannelFor(mumble_server, game, server, team)
self.assertEqual(3, cid - prev)
@@ -325,42 +334,42 @@ class Test(unittest.TestCase):
sid = mumble_server.id()
self.assertEqual(self.s.db.cidFor(sid, game), prev + 1);
self.assertEqual(self.s.db.cidFor(sid, game, server), prev + 2);
self.assertEqual(self.s.db.cidFor(sid, game, server, team), prev + 3);
self.assertEqual(self.s.db.cidFor(sid, game), prev + 1)
self.assertEqual(self.s.db.cidFor(sid, game, server), prev + 2)
self.assertEqual(self.s.db.cidFor(sid, game, server, team), prev + 3)
gotcid = self.s.getOrCreateChannelFor(mumble_server, game, server, team)
self.assertEqual(cid, gotcid)
c = mumble_server.channels
self.checkACLThings(c[prev + 3].acls, [{'group' : '~source_tf_[A-1:123]_3'}])
self.checkACLThings(c[prev + 2].acls, [{'group' : '~source_tf_[A-1:123]'}])
self.checkACLThings(c[prev + 3].acls, [{'group': '~source_tf_[A-1:123]_3'}])
self.checkACLThings(c[prev + 2].acls, [{'group': '~source_tf_[A-1:123]'}])
self.checkACLThings(c[prev + 1].acls, [{},
{'group' : '~source_tf'}])
{'group': '~source_tf'}])
#print self.s.db.db.execute("SELECT * FROM source").fetchall()
# print self.s.db.db.execute("SELECT * FROM source").fetchall()
def testGetGameName(self):
self.resetState()
self.assertEqual(self.s.getGameName("tf"), "Team Fortress 2")
self.assertEqual(self.s.getGameName("invalid"), "%(game)s");
self.assertEqual(self.s.getGameName("invalid"), "%(game)s")
def testGetServerName(self):
self.resetState()
self.assertEqual(self.s.getServerName("tf"), "Test %(game)s %(server)s")
self.assertEqual(self.s.getServerName("invalid"), "%(server)s");
self.assertEqual(self.s.getServerName("invalid"), "%(server)s")
def testGetTeamName(self):
self.resetState()
self.assertEqual(self.s.getTeamName("tf", 2), "Blue")
self.assertEqual(self.s.getTeamName("tf", 100), "100") #oob
self.assertEqual(self.s.getTeamName("tf", 100), "100") # oob
self.assertEqual(self.s.getTeamName("invalid", 2), "Team one")
self.assertEqual(self.s.getTeamName("invalid", 100), "100") #oob
self.assertEqual(self.s.getTeamName("invalid", 100), "100") # oob
def testValidGameType(self):
self.resetState()
@@ -403,7 +412,7 @@ class Test(unittest.TestCase):
TEAM_RED_SID = prev + 3
TEAM_BLUE_SID = prev + 4
user = source.User(user_state, {'team':TEAM_BLUE}, "tf", "[A-1:123]")
user = source.User(user_state, {'team': TEAM_BLUE}, "tf", "[A-1:123]")
self.s.moveUser(self.mserv, user)
c = mumble_server.channels
self.assertEqual(c[prev + 1].parent, BASE_SID)
@@ -437,67 +446,72 @@ class Test(unittest.TestCase):
self.s.db.registerChannel(6, 9, "8", "9", 10)
self.s.db.registerChannel(5, 10, "7", "123", 9)
game = 'cstrike'; server = '[A123:123]'; team = 1
game = 'cstrike'
server = '[A123:123]'
team = 1
self.s.getOrCreateChannelFor(self.mserv, game, server, team)
self.s.validateChannelDB()
self.assertEqual(len(self.s.db.registeredChannels()), 3)
def testSetACLsForGameChannel(self):
self.resetState()
mumble_server = self.mserv
cid = mumble_server.addChannel("test", 1); game = "dod"
cid = mumble_server.addChannel("test", 1)
game = "dod"
self.s.setACLsForGameChannel(mumble_server, cid, game)
acls = mumble_server.channels[cid].acls
self.checkACLThings(acls, [{'applyHere' : True,
'applySubs' : True,
'userid' : -1,
'group' : 'all',
'deny' : ACLS.ALL,
'allow' : 0},
{'applyHere' : True,
'applySubs' : False,
'userid' : -1,
'group' : '~source_dod',
'deny' : 0,
'allow' : ACLS.EAT}])
self.checkACLThings(acls, [{'applyHere': True,
'applySubs': True,
'userid': -1,
'group': 'all',
'deny': ACLS.ALL,
'allow': 0},
{'applyHere': True,
'applySubs': False,
'userid': -1,
'group': '~source_dod',
'deny': 0,
'allow': ACLS.EAT}])
def testSetACLsForServerChannel(self):
self.resetState()
mumble_server = self.mserv
cid = mumble_server.addChannel("test", 1); game = "tf"; server = "[A-1:SomeServer]"
cid = mumble_server.addChannel("test", 1)
game = "tf"
server = "[A-1:SomeServer]"
self.s.setACLsForServerChannel(mumble_server, cid, game, server)
acls = mumble_server.channels[cid].acls
self.checkACLThings(acls, [{'applyHere' : True,
'applySubs' : False,
'userid' : -1,
'group' : '~source_tf_[A-1:SomeServer]',
'deny' : 0,
'allow' : ACLS.EAT}])
self.checkACLThings(acls, [{'applyHere': True,
'applySubs': False,
'userid': -1,
'group': '~source_tf_[A-1:SomeServer]',
'deny': 0,
'allow': ACLS.EAT}])
def testSetACLsForTeamChannel(self):
self.resetState()
mumble_server = self.mserv
cid = mumble_server.addChannel("test", 1); game = "tf"; server = "[A-1:SomeServer]"; team = 2
cid = mumble_server.addChannel("test", 1)
game = "tf"
server = "[A-1:SomeServer]"
team = 2
self.s.setACLsForTeamChannel(mumble_server, cid, game, server, team)
acls = mumble_server.channels[cid].acls
self.checkACLThings(acls, [{'applyHere' : True,
'applySubs' : False,
'userid' : -1,
'group' : '~source_tf_[A-1:SomeServer]_2',
'deny' : 0,
'allow' : ACLS.ALL}])
self.checkACLThings(acls, [{'applyHere': True,
'applySubs': False,
'userid': -1,
'group': '~source_tf_[A-1:SomeServer]_2',
'deny': 0,
'allow': ACLS.ALL}])
def testAddToGroups(self):
self.resetState()
@@ -505,7 +519,10 @@ class Test(unittest.TestCase):
mumble_server = self.mserv
prev = mumble_server._lastChannelID()
session = 10; game = 'cstrike'; server = '[A-1:12345]'; team = 1
session = 10
game = 'cstrike'
server = '[A-1:12345]'
team = 1
self.s.getOrCreateChannelFor(mumble_server, game, server, team)
# Test
@@ -521,10 +538,12 @@ class Test(unittest.TestCase):
mumble_server = self.mserv
game = 'cstrike'; server = '[A-1:12345]'; team = 1
game = 'cstrike'
server = '[A-1:12345]'
team = 1
self.s.getOrCreateChannelFor(mumble_server, game, server, team)
cids = []
for c in mumble_server.channels.itervalues():
for c in mumble_server.channels.values():
c.name = str(c.id)
self.s.channelStateChanged(mumble_server, c)
cids.append(c.id)
@@ -538,7 +557,8 @@ class Test(unittest.TestCase):
for cid in cids:
self.assertEqual(mumble_server._getChan(cid).name, str(cid))
if __name__ == "__main__":
#logging.basicConfig(level = logging.DEBUG)
#import sys;sys.argv = ['', 'Test.testName']
# logging.basicConfig(level = logging.DEBUG)
# import sys;sys.argv = ['', 'Test.testName']
unittest.main()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -34,6 +34,7 @@ class User(object):
User to hold state as well as parsed data fields in a
sane fashion.
"""
def __init__(self, state, identity=None, game=None, server=None):
self.state = state
self.identity = identity or {}
@@ -67,6 +68,7 @@ class User(object):
self.game = game
self.server = server
class UserRegistry(object):
"""
Registry to store User objects for given servers
@@ -89,10 +91,10 @@ class UserRegistry(object):
"""
Add new user to registry
"""
assert(isinstance(user, User))
assert (isinstance(user, User))
if not sid in self.users:
self.users[sid] = {session:user}
self.users[sid] = {session: user}
elif not session in self.users[sid]:
self.users[sid][session] = user
else:
@@ -103,10 +105,10 @@ class UserRegistry(object):
"""
Add user or overwrite existing one (identified by sid + session)
"""
assert(isinstance(user, User))
assert (isinstance(user, User))
if not sid in self.users:
self.users[sid] = {session:user}
self.users[sid] = {session: user}
else:
self.users[sid][session] = user
@@ -126,9 +128,8 @@ class UserRegistry(object):
"""
Return true if any user in the registry is occupying the given channel
"""
for user in self.users[sid].itervalues():
for user in self.users[sid].values():
if user.state and user.state.channel == cid:
return True
return False

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -31,16 +31,20 @@
import unittest
from users import User, UserRegistry
from .users import User, UserRegistry
class Test(unittest.TestCase):
def getSomeUsers(self, n =5):
sid = []; session = []; user = []
def getSomeUsers(self, n=5):
sid = []
session = []
user = []
for i in range(n):
s=str(i)
sid.append(i) ; session.append(i)
user.append(User("state"+s, "identity"+s, "game"+s, "server"+s))
s = str(i)
sid.append(i)
session.append(i)
user.append(User("state" + s, "identity" + s, "game" + s, "server" + s))
return sid, session, user
@@ -71,11 +75,11 @@ class Test(unittest.TestCase):
self.assertEqual(r.get(sid[0], session[0]), None)
def testUser(self):
u = User("State", {'team':2} , "tf", "Someserver")
u = User("State", {'team': 2}, "tf", "Someserver")
self.assertTrue(u.valid())
self.assertFalse(User("State").valid())
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
# import sys;sys.argv = ['', 'Test.testName']
unittest.main()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -36,16 +36,14 @@
# debugging purposes. Usually you don't want
# to use this.
#
from mumo_module import MumoModule, logModFu
from mumo_module import (x2bool,
MumoModule,
logModFu)
class test(MumoModule):
default_config = {'testing':(('tvar', int , 1),
default_config = {'testing': (('tvar', int, 1),
('novar', str, 'no bernd'))}
def __init__(self, name, manager, configuration = None):
def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration)
log = self.log()
cfg = self.cfg()
@@ -64,31 +62,32 @@ class test(MumoModule):
@logModFu
def disconnected(self):
self.log().debug("Ice disconnected")
#
#--- Meta callback functions
# --- Meta callback functions
#
@logModFu
def started(self, server, context = None):
def started(self, server, context=None):
pass
@logModFu
def stopped(self, server, context = None):
def stopped(self, server, context=None):
pass
#
#--- Server callback functions
# --- Server callback functions
#
@logModFu
def userConnected(self, server, state, context = None):
def userConnected(self, server, state, context=None):
pass
@logModFu
def userDisconnected(self, server, state, context = None):
def userDisconnected(self, server, state, context=None):
pass
@logModFu
def userStateChanged(self, server, state, context = None):
def userStateChanged(self, server, state, context=None):
pass
@logModFu
@@ -96,20 +95,20 @@ class test(MumoModule):
pass
@logModFu
def channelCreated(self, server, state, context = None):
def channelCreated(self, server, state, context=None):
pass
@logModFu
def channelRemoved(self, server, state, context = None):
def channelRemoved(self, server, state, context=None):
pass
@logModFu
def channelStateChanged(self, server, state, context = None):
def channelStateChanged(self, server, state, context=None):
pass
#
#--- Server context callback functions
# --- Server context callback functions
#
@logModFu
def contextAction(self, server, action, user, session, channelid, context = None):
def contextAction(self, server, action, user, session, channelid, context=None):
pass

117
mumo.py
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010-2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,18 +29,10 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
import os
import sys
import Ice
import IcePy
import logging
import tempfile
from config import (Config,
x2bool,
commaSeperatedIntegers)
from threading import Timer
from optparse import OptionParser
from logging import (debug,
info,
warning,
@@ -48,14 +40,22 @@ from logging import (debug,
critical,
exception,
getLogger)
from optparse import OptionParser
from threading import Timer
import Ice
import IcePy
from config import (Config,
commaSeperatedIntegers)
from mumo_manager import MumoManager
#
#--- Default configuration values
# --- Default configuration values
#
cfgfile = 'mumo.ini'
default = MumoManager.cfg_default.copy()
default.update({'ice':(('host', str, '127.0.0.1'),
default.update({'ice': (('host', str, '127.0.0.1'),
('port', int, 6502),
('slice', str, ''),
('secret', str, ''),
@@ -64,15 +64,16 @@ default.update({'ice':(('host', str, '127.0.0.1'),
('callback_host', str, '127.0.0.1'),
('callback_port', int, -1)),
'iceraw':None,
'murmur':(('servers', commaSeperatedIntegers, []),),
'system':(('pidfile', str, 'mumo.pid'),),
'log':(('level', int, logging.DEBUG),
'iceraw': None,
'murmur': (('servers', commaSeperatedIntegers, []),),
'system': (('pidfile', str, 'mumo.pid'),),
'log': (('level', int, logging.DEBUG),
('file', str, 'mumo.log'))})
def load_slice(slice):
#
#--- Loads a given slicefile, used by dynload_slice and fsload_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.
#
@@ -89,9 +90,10 @@ def load_slice(slice):
Ice.loadSlice('', slicedirs + [slice])
def dynload_slice(prx):
#
#--- Dynamically retrieves the slice file from the target server
# --- Dynamically retrieves the slice file from the target server
#
info("Loading slice from server")
try:
@@ -99,23 +101,25 @@ def dynload_slice(prx):
# In case it breaks with future versions use slice2py and search for
# "IcePy.Operation('getSlice'," for updates in the generated bindings.
op = None
if IcePy.intVersion() < 30500L:
if IcePy.intVersion() < 30500:
# Old 3.4 signature with 9 parameters
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, (), (), (), IcePy._t_string, ())
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, (), (),
(), IcePy._t_string, ())
else:
# New 3.5 signature with 10 parameters.
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, None, (), (), (), ((), IcePy._t_string, False, 0), ())
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, None, (),
(), (), ((), IcePy._t_string, False, 0), ())
slice = op.invoke(prx, ((), None))
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix = '.ice')
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix='.ice')
dynslicefile = os.fdopen(dynslicefiledesc, 'w')
dynslicefile.write(slice)
dynslicefile.flush()
load_slice(dynslicefilepath)
dynslicefile.close()
os.remove(dynslicefilepath)
except Exception, e:
except Exception as e:
error("Retrieving slice from server failed")
exception(e)
raise
@@ -123,14 +127,15 @@ def dynload_slice(prx):
def fsload_slice(slice):
#
#--- Load slice from file system
# --- Load slice from file system
#
debug("Loading slice from filesystem: %s" % slice)
load_slice(slice)
def do_main_program():
#
#--- Moderator implementation
# --- Moderator implementation
# All of this has to go in here so we can correctly daemonize the tool
# without loosing the file descriptors opened by the Ice module
@@ -153,6 +158,7 @@ def do_main_program():
else:
fsload_slice(cfg.ice.slice)
# noinspection PyUnresolvedReferences
import Murmur
class mumoIceApp(Ice.Application):
@@ -201,7 +207,8 @@ def do_main_program():
else:
cbp = ''
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', 'tcp -h %s%s' % (cfg.ice.callback_host, cbp))
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client',
'tcp -h %s%s' % (cfg.ice.callback_host, cbp))
adapter.activate()
self.adapter = adapter
self.manager.setClientAdapter(adapter)
@@ -230,11 +237,12 @@ def do_main_program():
servercb = Murmur.ServerCallbackPrx.uncheckedCast(servercbprx)
server.addCallback(servercb)
except (Murmur.InvalidSecretException, Ice.UnknownUserException, Ice.ConnectionRefusedException), e:
except (Murmur.InvalidSecretException, Ice.UnknownUserException, Ice.ConnectionRefusedException) as e:
if isinstance(e, Ice.ConnectionRefusedException):
error('Server refused connection')
elif isinstance(e, Murmur.InvalidSecretException) or \
isinstance(e, Ice.UnknownUserException) and (e.unknown == 'Murmur::InvalidSecretException'):
isinstance(e, Ice.UnknownUserException) and (
e.unknown == 'Murmur::InvalidSecretException'):
error('Invalid ice secret')
else:
# We do not actually want to handle this one, re-raise it
@@ -253,7 +261,7 @@ def do_main_program():
Tries to retrieve the server uptime to determine wheter the server is
still responsive or has restarted in the meantime
"""
#debug('Watchdog run')
# debug('Watchdog run')
try:
uptime = self.meta.getUptime()
if self.metaUptime > 0:
@@ -266,8 +274,9 @@ def do_main_program():
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)
except Ice.Exception as e:
error('Connection to server lost, will try to reestablish callbacks in next watchdog run (%ds)',
cfg.ice.watchdog)
debug(str(e))
self.attachCallbacks()
@@ -306,11 +315,12 @@ def do_main_program():
The default is to catch all non-Ice exceptions.
"""
def newdec(func):
def newfunc(*args, **kws):
try:
return func(*args, **kws)
except Exception, e:
except Exception as e:
catch = True
for ex in exceptions:
if isinstance(e, ex):
@@ -324,6 +334,7 @@ def do_main_program():
raise
return newfunc
return newdec
class metaCallback(Murmur.MetaCallback):
@@ -347,7 +358,7 @@ def do_main_program():
server.addCallback(servercb)
# Apparently this server was restarted without us noticing
except (Murmur.InvalidSecretException, Ice.UnknownUserException), e:
except (Murmur.InvalidSecretException, Ice.UnknownUserException) as e:
if hasattr(e, "unknown") and e.unknown != "Murmur::InvalidSecretException":
# Special handling for Murmur 1.2.2 servers with invalid slice files
raise e
@@ -382,10 +393,10 @@ def do_main_program():
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)
return new_fu
class serverCallback(Murmur.ServerCallback):
@@ -405,24 +416,30 @@ def do_main_program():
@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
@checkSecret
@forwardServer
def userTextMessage(self, u, m, current=None) : pass
def userTextMessage(self, u, m, current=None): pass
class customContextCallback(Murmur.ServerContextCallback):
def __init__(self, contextActionCallback, *ctx):
@@ -436,7 +453,7 @@ def do_main_program():
self.cb(*(self.ctx + args), **argv)
#
#--- Start of moderator
# --- Start of moderator
#
info('Starting mumble moderator')
debug('Initializing manager')
@@ -454,6 +471,7 @@ def do_main_program():
info('Shutdown complete')
return state
class CustomLogger(Ice.Logger):
"""
Logger implementation to pipe Ice log messages into
@@ -476,8 +494,9 @@ class CustomLogger(Ice.Logger):
def error(self, message):
self._log.error(message)
#
#--- Start of program
# --- Start of program
#
if __name__ == '__main__':
# Parse commandline options
@@ -501,22 +520,21 @@ if __name__ == '__main__':
# Load configuration
try:
cfg = Config(option.ini, default)
except Exception, e:
print >> sys.stderr, 'Fatal error, could not load config file from "%s"' % cfgfile
print >> sys.stderr, e
except Exception as e:
print('Fatal error, could not load config file from "%s"' % cfgfile, file=sys.stderr)
print(e, file=sys.stderr)
sys.exit(1)
# Initialise logger
if cfg.log.file:
try:
logfile = open(cfg.log.file, 'a')
except IOError, e:
#print>>sys.stderr, str(e)
print >> sys.stderr, 'Fatal error, could not open logfile "%s"' % cfg.log.file
except IOError as e:
# print>>sys.stderr, str(e)
print('Fatal error, could not open logfile "%s"' % cfg.log.file, file=sys.stderr)
sys.exit(1)
else:
logfile = logging.sys.stderr
logfile = logging.sys.stdout
if option.verbose:
level = cfg.log.level
@@ -533,14 +551,15 @@ if __name__ == '__main__':
if option.force_app:
raise ImportError # Pretend that we couldn't import the daemon lib
import daemon
try:
from daemon.pidfile import TimeoutPIDLockFile
except ImportError: # Version < 1.6
from daemon.pidlockfile import TimeoutPIDLockFile
except ImportError:
if option.force_daemon:
print >> sys.stderr, 'Fatal error, could not daemonize process due to missing "daemon" library, ' \
'please install the missing dependency and restart the application'
print('Fatal error, could not daemonize process due to missing "daemon" library, '
'please install the missing dependency and restart the application', file=sys.stderr)
sys.exit(1)
ret = do_main_program()
else:
@@ -548,10 +567,10 @@ if __name__ == '__main__':
if pidfile.is_locked():
try:
os.kill(pidfile.read_pid(), 0)
print >> sys.stderr, 'Mumo already running as %s' % pidfile.read_pid()
print('Mumo already running as %s' % pidfile.read_pid(), file=sys.stderr)
sys.exit(1)
except OSError:
print >> sys.stderr, 'Found stale mumo pid file but no process, breaking lock'
print('Found stale mumo pid file but no process, breaking lock', file=sys.stderr)
pidfile.break_lock()
context = daemon.DaemonContext(working_directory=sys.path[0],

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,31 +29,37 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import Queue
from worker import Worker, local_thread, local_thread_blocking
from config import Config
import sys
import os
import queue
import sys
import uuid
from config import Config
from worker import Worker, local_thread, local_thread_blocking
class FailedLoadModuleException(Exception):
pass
class FailedLoadModuleConfigException(FailedLoadModuleException):
pass
class FailedLoadModuleImportException(FailedLoadModuleException):
pass
class FailedLoadModuleInitializationException(FailedLoadModuleException):
pass
def debug_log(enable = True):
def debug_log(enable=True):
def new_dec(fu):
def new_fu(*args, **kwargs):
self = args[0]
log = self.log()
skwargs = ','.join(['%s=%s' % (karg,repr(arg)) for karg, arg in kwargs])
skwargs = ','.join(['%s=%s' % (karg, repr(arg)) for karg, arg in kwargs])
sargs = ','.join([str(arg) for arg in args[1:]]) + '' if not skwargs else (',' + str(skwargs))
call = "%s(%s)" % (fu.__name__, sargs)
@@ -61,13 +67,15 @@ def debug_log(enable = True):
res = fu(*args, **kwargs)
log.debug("%s -> %s", call, repr(res))
return res
return new_fu if enable else fu
return new_dec
debug_me = True
class MumoManagerRemote(object):
"""
Manager object handed to MumoModules. This module
@@ -88,7 +96,7 @@ class MumoManagerRemote(object):
def getQueue(self):
return self.__queue
def subscribeMetaCallbacks(self, handler, servers = SERVERS_ALL):
def subscribeMetaCallbacks(self, handler, servers=SERVERS_ALL):
"""
Subscribe to meta callbacks. Subscribes the given handler to the following
callbacks:
@@ -102,7 +110,7 @@ class MumoManagerRemote(object):
"""
return self.__master.subscribeMetaCallbacks(self.__queue, handler, servers)
def unsubscribeMetaCallbacks(self, handler, servers = SERVERS_ALL):
def unsubscribeMetaCallbacks(self, handler, servers=SERVERS_ALL):
"""
Unsubscribe from meta callbacks. Unsubscribes the given handler from callbacks
for the given servers.
@@ -113,7 +121,7 @@ class MumoManagerRemote(object):
"""
return self.__master.unscubscribeMetaCallbacks(self.__queue, handler, servers)
def subscribeServerCallbacks(self, handler, servers = SERVERS_ALL):
def subscribeServerCallbacks(self, handler, servers=SERVERS_ALL):
"""
Subscribe to server callbacks. Subscribes the given handler to the following
callbacks:
@@ -131,7 +139,7 @@ class MumoManagerRemote(object):
"""
return self.__master.subscribeServerCallbacks(self.__queue, handler, servers)
def unsubscribeServerCallbacks(self, handler, servers = SERVERS_ALL):
def unsubscribeServerCallbacks(self, handler, servers=SERVERS_ALL):
"""
Unsubscribe from server callbacks. Unsubscribes the given handler from callbacks
for the given servers.
@@ -171,7 +179,8 @@ class MumoManagerRemote(object):
@param action: Action identifier passed to your callback (see above)
@param text: Text for the menu entry
@param handler: Handler function to call when the menu item is used
@param context: Contexts to show entry in (can be a combination of ContextServer, ContextChannel and ContextUser)
@param context: Contexts to show entry in (can be a combination of ContextServer, ContextChannel and
ContextUser)
"""
server_actions = self.__context_callbacks.get(server.id())
@@ -240,11 +249,11 @@ class MumoManagerRemote(object):
class MumoManager(Worker):
MAGIC_ALL = -1
cfg_default = {'modules':(('mod_dir', str, "modules/"),
cfg_default = {'modules': (('mod_dir', str, "modules/"),
('cfg_dir', str, "modules-enabled/"),
('timeout', int, 2))}
def __init__(self, murmur, context_callback_type, cfg = Config(default = cfg_default)):
def __init__(self, murmur, context_callback_type, cfg=Config(default=cfg_default)):
Worker.__init__(self, "MumoManager")
self.queues = {} # {queue:module}
self.modules = {} # {name:module}
@@ -279,13 +288,13 @@ class MumoManager(Worker):
else:
mdict[server][queue] = [handler]
else:
mdict[server] = {queue:[handler]}
mdict[server] = {queue: [handler]}
def __rem_from_dict(self, mdict, queue, handler, servers):
for server in servers:
try:
mdict[server][queue].remove(handler)
except KeyError, ValueError:
except KeyError as ValueError:
pass
def __announce_to_dict(self, mdict, server, function, *args, **kwargs):
@@ -302,13 +311,13 @@ class MumoManager(Worker):
# Announce to all handlers of the given serverlist
if server == self.MAGIC_ALL:
servers = mdict.iterkeys()
servers = iter(mdict.keys())
else:
servers = [self.MAGIC_ALL, server]
for server in servers:
try:
for queue, handlers in mdict[server].iteritems():
for queue, handlers in mdict[server].items():
for handler in handlers:
self.__call_remote(queue, handler, function, *args, **kwargs)
except KeyError:
@@ -319,29 +328,29 @@ class MumoManager(Worker):
try:
func = getattr(handler, function) # Find out what to call on target
queue.put((None, func, args, kwargs))
except AttributeError, e:
except AttributeError as e:
mod = self.queues.get(queue, None)
myname = ""
for name, mymod in self.modules.iteritems():
for name, mymod in self.modules.items():
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)
#
#-- Module multiplexing functionality
# -- Module multiplexing functionality
#
@local_thread
def announceConnected(self, meta = None):
def announceConnected(self, meta=None):
"""
Call connected handler on all handlers
"""
self.meta = meta
for queue, module in self.queues.iteritems():
for queue, module in self.queues.items():
self.__call_remote(queue, module, "connected")
@local_thread
@@ -349,7 +358,7 @@ class MumoManager(Worker):
"""
Call disconnected handler on all handlers
"""
for queue, module in self.queues.iteritems():
for queue, module in self.queues.items():
self.__call_remote(queue, module, "disconnected")
@local_thread
@@ -377,7 +386,7 @@ class MumoManager(Worker):
self.__announce_to_dict(self.serverCallbacks, server, function, *args, **kwargs)
#
#--- Module self management functionality
# --- Module self management functionality
#
@local_thread
@@ -439,11 +448,11 @@ class MumoManager(Worker):
"""
return self.meta
#--- Module load/start/stop/unload functionality
# --- Module load/start/stop/unload functionality
#
@local_thread_blocking
@debug_log(debug_me)
def loadModules(self, names = None):
def loadModules(self, names=None):
"""
Loads a list of modules from the mumo directory structure by name.
@@ -476,23 +485,23 @@ class MumoManager(Worker):
return loadedmodules
@local_thread_blocking
def loadModuleCls(self, name, modcls, module_cfg = None):
def loadModuleCls(self, name, modcls, module_cfg=None):
return self._loadModuleCls_noblock(name, modcls, module_cfg)
@debug_log(debug_me)
def _loadModuleCls_noblock(self, name, modcls, module_cfg = None):
def _loadModuleCls_noblock(self, name, modcls, module_cfg=None):
log = self.log()
if name in self.modules:
log.error("Module '%s' already loaded", name)
return
modqueue = Queue.Queue()
modqueue = queue.Queue()
modmanager = MumoManagerRemote(self, name, modqueue)
try:
modinst = modcls(name, modmanager, module_cfg)
except Exception, e:
except Exception as e:
msg = "Module '%s' failed to initialize" % name
log.error(msg)
log.exception(e)
@@ -543,7 +552,7 @@ class MumoManager(Worker):
try:
mod = __import__(name)
self.imports[name] = mod
except ImportError, e:
except ImportError as e:
msg = "Failed to import module '%s', reason: %s" % (name, str(e))
log.error(msg)
raise FailedLoadModuleImportException(msg)
@@ -563,7 +572,7 @@ class MumoManager(Worker):
@local_thread_blocking
@debug_log(debug_me)
def startModules(self, names = None):
def startModules(self, names=None):
"""
Start a module by name
@@ -575,12 +584,12 @@ class MumoManager(Worker):
if not names:
# If no names are given start all models
names = self.modules.iterkeys()
names = iter(self.modules.keys())
for name in names:
try:
modinst = self.modules[name]
if not modinst.isAlive():
if not modinst.is_alive():
modinst.start()
log.debug("Module '%s' started", name)
else:
@@ -593,7 +602,7 @@ class MumoManager(Worker):
@local_thread_blocking
@debug_log(debug_me)
def stopModules(self, names = None, force = False):
def stopModules(self, names=None, force=False):
"""
Stop a list of modules by name. Note that this only works
for well behaved modules. At this point if a module is really going
@@ -608,7 +617,7 @@ class MumoManager(Worker):
if not names:
# If no names are given start all models
names = self.modules.iterkeys()
names = iter(self.modules.keys())
for name in names:
try:
@@ -620,29 +629,29 @@ class MumoManager(Worker):
if force:
# We will have to drain the modules queues
for queue, module in self.queues.iteritems():
for queue, module in self.queues.items():
if module in self.modules:
try:
while queue.get_nowait(): pass
except Queue.Empty: pass
except queue.Empty:
pass
for modinst in stoppedmodules.itervalues():
if modinst.isAlive():
for modinst in stoppedmodules.values():
if modinst.is_alive():
modinst.stop()
log.debug("Module '%s' is being stopped", name)
else:
log.debug("Module '%s' already stopped", name)
for modinst in stoppedmodules.itervalues():
modinst.join(timeout = self.cfg.modules.timeout)
for modinst in stoppedmodules.values():
modinst.join(timeout=self.cfg.modules.timeout)
return stoppedmodules
def stop(self, force = True):
def stop(self, force=True):
"""
Stops all modules and shuts down the manager.
"""
self.log().debug("Stopping")
self.stopModules()
Worker.stop(self, force)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -30,13 +30,12 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
import Queue
from mumo_manager import MumoManager, MumoManagerRemote
from mumo_module import MumoModule
from logging import basicConfig, ERROR, getLogger
import logging
from logging import getLogger
from threading import Event
from mumo_manager import MumoManager
from mumo_module import MumoModule
class MumoManagerTest(unittest.TestCase):
def setUp(self):
@@ -44,7 +43,7 @@ class MumoManagerTest(unittest.TestCase):
l.disabled = True
class MyModule(MumoModule):
def __init__(self, name, manager, configuration = None):
def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration)
self.estarted = Event()
@@ -66,7 +65,6 @@ class MumoManagerTest(unittest.TestCase):
man = self.manager()
man.subscribeMetaCallbacks(self)
man.subscribeServerCallbacks(self)
man.subscribeContextCallbacks(self)
self.econnected.set()
def disconnected(self):
@@ -93,16 +91,16 @@ class MumoManagerTest(unittest.TestCase):
self.cfg.test = 10
#
#--- Helpers for independent test env creation
# --- Helpers for independent test env creation
#
def up(self):
man = MumoManager(None)
man = MumoManager(None, None)
man.start()
mod = man.loadModuleCls("MyModule", self.mymod, self.cfg)
man.startModules()
return (man, mod)
return man, mod
def down(self, man, mod):
man.stopModules()
@@ -110,23 +108,23 @@ class MumoManagerTest(unittest.TestCase):
man.join(timeout=1)
#
#--- Tests
# --- Tests
#
def testModuleStarted(self):
man, mod = self.up()
mod.estarted.wait(timeout=1)
assert(mod.estarted.is_set())
assert (mod.estarted.is_set())
self.down(man, mod)
def testModuleStopStart(self):
man ,mod = self.up()
man, mod = self.up()
tos = ["MyModule"]
self.assertEquals(list(man.stopModules(tos).iterkeys()), tos)
self.assertEqual(list(man.stopModules(tos).keys()), tos)
mod.estopped.wait(timeout=1)
assert(mod.estopped.is_set())
assert (mod.estopped.is_set())
self.down(man, mod)
@@ -135,10 +133,10 @@ class MumoManagerTest(unittest.TestCase):
man.announceConnected()
mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set())
assert (mod.econnected.is_set())
man.announceDisconnected()
mod.edisconnected.wait(timeout=1)
assert(mod.edisconnected.is_set())
assert (mod.edisconnected.is_set())
self.down(man, mod)
@@ -146,32 +144,33 @@ class MumoManagerTest(unittest.TestCase):
man, mod = self.up()
man.announceConnected()
mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set())
man.announceMeta(man.MAGIC_ALL, "metaCallMe", "arg1", arg2 = "arg2")
assert (mod.econnected.is_set())
man.announceMeta(man.MAGIC_ALL, "metaCallMe", "arg1", arg2="arg2")
mod.emeta.wait(timeout=1)
assert(mod.emeta.is_set())
assert (mod.emeta.is_set())
man.announceDisconnected()
self.down(man, mod)
def testContextCallback(self):
man, mod = self.up()
man.announceConnected()
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)
# FIXME: Test ContextCallbacks correctly
# def testContextCallback(self):
# man, mod = self.up()
# man.announceConnected()
# 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()
mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set())
man.announceServer(man.MAGIC_ALL, "serverCallMe", "server", "arg1", arg2 = "arg2")
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())
assert (mod.eserver.is_set())
man.announceDisconnected()
self.down(man, mod)
@@ -180,5 +179,5 @@ class MumoManagerTest(unittest.TestCase):
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
# import sys;sys.argv = ['', 'Test.testName']
unittest.main()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,27 +29,24 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from config import (Config,
x2bool,
commaSeperatedIntegers,
commaSeperatedStrings,
commaSeperatedBool)
from config import (Config)
from worker import Worker
class MumoModule(Worker):
default_config = {}
def __init__(self, name, manager, configuration = None):
def __init__(self, name, manager, configuration=None):
Worker.__init__(self, name, manager.getQueue())
self.__manager = manager
if isinstance(configuration, basestring):
if isinstance(configuration, str):
# If we are passed a string expect a config file there
if configuration:
self.__cfg = Config(configuration, self.default_config)
elif self.default_config:
self.__cfg = Config(default = self.default_config)
self.__cfg = Config(default=self.default_config)
else:
self.__cfg = None
else:
@@ -58,15 +55,14 @@ class MumoModule(Worker):
self.log().info("Initialized")
#--- Accessors
# --- Accessors
def manager(self):
return self.__manager
def cfg(self):
return self.__cfg
#--- Module control
# --- Module control
def onStart(self):
self.log().info("Start")
@@ -74,7 +70,7 @@ class MumoModule(Worker):
def onStop(self):
self.log().info("Stop")
#--- Events
# --- Events
def connected(self):
# Called once the Ice connection to the murmur server
@@ -94,8 +90,9 @@ class MumoModule(Worker):
def logModFu(fu):
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())
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.items())
log.debug("%s(%s%s%s)", fu.__name__, str(self), argss, kwargss)
return fu(self, *args, **kwargs)
return new_fu

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -30,15 +30,7 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
if __name__ == "__main__":
import unittest
from worker_test import *
from config_test import *
from mumo_manager_test import *
from modules.source.source_test import *
from modules.source.users_test import *
from modules.source.db_test import *
#import sys;sys.argv = ['', 'Test.testName']
# import sys;sys.argv = ['', 'Test.testName']
unittest.main()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -39,35 +39,33 @@ import sys
import tempfile
from optparse import OptionParser
# Default settings
import Ice
import IcePy
# Default settings
if __name__ == "__main__":
parser = OptionParser()
parser.add_option('-t', '--target',
help = 'Host to connect to', default = "127.0.0.1")
help='Host to connect to', default="127.0.0.1")
parser.add_option('-p', '--port',
help = 'Port to connect to', default = "6502")
help='Port to connect to', default="6502")
parser.add_option('-b', '--base',
help = 'Channel id of the base channel', default = '0')
help='Channel id of the base channel', default='0')
parser.add_option('-v', '--vserver',
help = 'Virtual server id', default = '1')
help='Virtual server id', default='1')
parser.add_option('-i', '--ice',
help = 'Path to slice file', default = 'Murmur.ice')
help='Path to slice file', default='Murmur.ice')
parser.add_option('-s', '--secret',
help = 'Ice secret', default = '')
parser.add_option('-l', '--linkteams', action = 'store_true',
help = 'Link teams so opposing players can hear each other', default = False)
help='Ice secret', default='')
parser.add_option('-l', '--linkteams', action='store_true',
help='Link teams so opposing players can hear each other', default=False)
parser.add_option('-n', '--name',
help = 'Treename', default = 'BF2')
parser.add_option('-o', '--out', default = 'bf2.ini',
help = 'File to output configuration to')
help='Treename', default='BF2')
parser.add_option('-o', '--out', default='bf2.ini',
help='File to output configuration to')
parser.add_option('-d', '--slicedir',
help = 'System slice directory used when getSliceDir is not available', default = '/usr/share/slice')
help='System slice directory used when getSliceDir is not available', default='/usr/share/slice')
(option, args) = parser.parse_args()
host = option.target
@@ -75,21 +73,21 @@ if __name__ == "__main__":
try:
port = int(option.port)
except ValueError:
print "Port value '%s' is invalid" % option.port
print("Port value '%s' is invalid" % option.port)
sys.exit(1)
try:
basechan = int(option.base)
if basechan < 0: raise ValueError
except ValueError:
print "Base channel value '%s' invalid" % option.base
print("Base channel value '%s' invalid" % option.base)
sys.exit(1)
try:
sid = int(option.vserver)
if sid < 1: raise ValueError
except ValueError:
print "Virtual server id value '%s' invalid" % option.vserver
print("Virtual server id value '%s' invalid" % option.vserver)
sys.exit(1)
name = option.name
@@ -104,7 +102,8 @@ if __name__ == "__main__":
ice = Ice.initialize(idata)
prx = ice.stringToProxy(prxstr)
print "Done"
print("Done")
def lslice(slf):
if not hasattr(Ice, "getSliceDir"):
@@ -112,56 +111,56 @@ if __name__ == "__main__":
else:
Ice.loadSlice('', ['-I' + Ice.getSliceDir(), slf])
try:
print "Trying to retrieve slice dynamically from server...",
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, (), (), (), IcePy._t_string, ())
print("Trying to retrieve slice dynamically from server...", end=' ')
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, (), (), (),
IcePy._t_string, ())
if hasattr(Ice, "getSliceDir"):
slice = op.invoke(prx, ((), None))
else:
slice = op.invoke(prx, (), None)
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix = '.ice')
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix='.ice')
dynslicefile = os.fdopen(dynslicefiledesc, 'w')
dynslicefile.write(slice)
dynslicefile.flush()
lslice(dynslicefilepath)
dynslicefile.close()
os.remove(dynslicefilepath)
print "Success"
except Exception, e:
print "Failed"
print str(e)
print("Success")
except Exception as e:
print("Failed")
print(str(e))
slicefile = option.ice
print "Load slice (%s)..." % slicefile,
print("Load slice (%s)..." % slicefile, end=' ')
lslice(slicefile)
print "Done"
print("Done")
print "Import dynamically compiled murmur class...",
print("Import dynamically compiled murmur class...", end=' ')
import Murmur
print "Done"
print "Establish ice connection...",
print("Done")
print("Establish ice connection...", end=' ')
if secret:
print "[protected]...",
print("[protected]...", end=' ')
ice.getImplicitContext().put("secret", secret)
murmur = Murmur.MetaPrx.checkedCast(prx)
print "Done"
print("Done")
print "Get server...",
print("Get server...", end=' ')
server = murmur.getServer(sid)
print "Done (%d)" % sid
print("Done (%d)" % sid)
ini = {}
ini['mumble_server'] = sid
ini['name'] = name
ini['ipport_filter'] = '.*'
ini = {'mumble_server': sid, 'name': name, 'ipport_filter': '.*'}
print "Creating channel structure:"
print("Creating channel structure:")
ACL = Murmur.ACL
EAT = Murmur.PermissionEnter | Murmur.PermissionTraverse
W = Murmur.PermissionWhisper
S = Murmur.PermissionSpeak
print name
print(name)
ini['left'] = basechan
gamechan = server.addChannel(name, basechan)
@@ -172,21 +171,21 @@ if __name__ == "__main__":
# server.setACL(self, channelid, acls, groups, inherit, _ctx=None)
#
server.setACL(gamechan,
[ACL(applyHere = True,
applySubs = True,
userid = -1,
group = 'all',
deny = EAT | W | S),
ACL(applyHere = True,
applySubs = True,
userid = -1,
group = '~bf2_%s_game' % name,
allow = S),
ACL(applyHere = True,
applySubs = False,
userid = -1,
group = '~bf2_%s_game' % name,
allow = EAT | W)],
[ACL(applyHere=True,
applySubs=True,
userid=-1,
group='all',
deny=EAT | W | S),
ACL(applyHere=True,
applySubs=True,
userid=-1,
group='~bf2_%s_game' % name,
allow=S),
ACL(applyHere=True,
applySubs=False,
userid=-1,
group='~bf2_%s_game' % name,
allow=EAT | W)],
[], True)
gamechanstate = server.getChannelState(gamechan)
@@ -207,8 +206,8 @@ if __name__ == "__main__":
"eighth": "Squad 8",
"ninth": "Squad 9"
}
for team,team_name in teams.items():
print name + "/" + team_name
for team, team_name in list(teams.items()):
print(name + "/" + team_name)
cid = server.addChannel(team_name, gamechan)
teamchanstate = server.getChannelState(cid)
if option.linkteams:
@@ -217,73 +216,72 @@ if __name__ == "__main__":
ini[team] = cid
server.setACL(ini[team],
[ACL(applyHere = True,
applySubs = False,
userid = -1,
group = '~bf2_team',
allow = EAT | W)],
[ACL(applyHere=True,
applySubs=False,
userid=-1,
group='~bf2_team',
allow=EAT | W)],
[], True)
print name + "/" + team_name + "/Commander"
print(name + "/" + team_name + "/Commander")
cid = server.addChannel("Commander", ini[team])
teamchanstate.links.append(cid)
ini[team + "_commander"] = cid
server.setACL(ini[team + "_commander"],
[ACL(applyHere = True,
applySubs = False,
userid = -1,
group = '~bf2_commander',
allow = EAT | W),
ACL(applyHere = True,
applySubs = False,
userid = -1,
group = '~bf2_squad_leader',
allow = W)],
[ACL(applyHere=True,
applySubs=False,
userid=-1,
group='~bf2_commander',
allow=EAT | W),
ACL(applyHere=True,
applySubs=False,
userid=-1,
group='~bf2_squad_leader',
allow=W)],
[], True)
state = server.getChannelState(ini[team+"_commander"])
state = server.getChannelState(ini[team + "_commander"])
state.position = -1
server.setChannelState(state)
for squad,squad_name in id_to_squad_name.items():
print name + "/" + team_name + "/" + squad_name
for squad, squad_name in list(id_to_squad_name.items()):
print(name + "/" + team_name + "/" + squad_name)
cid = server.addChannel(squad_name, ini[team])
teamchanstate.links.append(cid)
ini[team + "_" + squad + "_squad"] = cid
ini[team + "_" + squad + "_squad_leader"] = ini[team + "_" + squad + "_squad"]
server.setACL(ini[team + "_" + squad + "_squad"],
[ACL(applyHere = True,
applySubs = False,
userid = -1,
group = '~bf2_%s_squad' % squad,
allow = EAT | W),
ACL(applyHere = True,
applySubs = False,
userid = -1,
group = '~bf2_commander',
allow = EAT | W),
ACL(applyHere = True,
applySubs = False,
userid = -1,
group = '~bf2_squad_leader',
allow = W)],
[ACL(applyHere=True,
applySubs=False,
userid=-1,
group='~bf2_%s_squad' % squad,
allow=EAT | W),
ACL(applyHere=True,
applySubs=False,
userid=-1,
group='~bf2_commander',
allow=EAT | W),
ACL(applyHere=True,
applySubs=False,
userid=-1,
group='~bf2_squad_leader',
allow=W)],
[], True)
server.setChannelState(teamchanstate)
server.setChannelState(gamechanstate)
print "Channel structure created"
print("Channel structure created")
print "Writing configuration to output file '%s'..." % option.out,
print("Writing configuration to output file '%s'..." % option.out, end=' ')
f = open(option.out, "w")
print>>f, "; Configuration created by mbf2man\n"
print>>f, "[bf2]\ngamecount = 1\n"
print>>f, "[g0]"
print("; Configuration created by mbf2man\n", file=f)
print("[bf2]\ngamecount = 1\n", file=f)
print("[g0]", file=f)
for key in sorted(ini):
value = ini[key]
print>>f, "%s = %s" % (key, value)
print("%s = %s" % (key, value), file=f)
f.close()
print "Done"
print("Done")

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,21 +29,25 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from threading import Thread
from Queue import Queue, Empty
from logging import getLogger
from queue import Queue, Empty
from threading import Thread
def local_thread(fu):
"""
Decorator which makes a function execute in the local worker thread
Return values are discarded
"""
def new_fu(*args, **kwargs):
self = args[0]
self.message_queue().put((None, fu, args, kwargs))
return new_fu
def local_thread_blocking(fu, timeout = None):
def local_thread_blocking(fu, timeout=None):
"""
Decorator which makes a function execute in the local worker thread
The function will block until return values are available or timeout
@@ -51,6 +55,7 @@ def local_thread_blocking(fu, timeout = None):
@param timeout Timeout in seconds
"""
def new_fu(*args, **kwargs):
self = args[0]
out = Queue()
@@ -65,7 +70,7 @@ def local_thread_blocking(fu, timeout = None):
class Worker(Thread):
def __init__(self, name, message_queue = None):
def __init__(self, name, message_queue=None):
"""
Implementation of a basic Queue based Worker thread.
@@ -73,13 +78,13 @@ class Worker(Thread):
@param message_queue Message queue on which to receive commands
"""
Thread.__init__(self, name = name)
Thread.__init__(self, name=name)
self.daemon = True
self.__in = message_queue if message_queue != None else Queue()
self.__log = getLogger(name)
self.__name = name
#--- Accessors
# --- Accessors
def log(self):
return self.__log
@@ -89,7 +94,7 @@ class Worker(Thread):
def message_queue(self):
return self.__in
#--- Overridable convience stuff
# --- Overridable convience stuff
def onStart(self):
"""
Override this function to perform actions on worker startup
@@ -101,31 +106,32 @@ class Worker(Thread):
Override this function to perform actions on worker shutdown
"""
pass
#--- Thread / Control
# --- Thread / Control
def run(self):
self.log().debug("Enter message loop")
self.onStart()
while True:
msg = self.__in.get()
if msg == None:
if msg is None:
break
(out, fu, args, kwargs) = msg
try:
res = fu(*args, **kwargs)
ex = None
except Exception, e:
except Exception as e:
self.log().exception(e)
res = None
ex = e
finally:
if not out is None:
if out is not None:
out.put((res, ex))
self.onStop()
self.log().debug("Leave message loop")
def stop(self, force = True):
def stop(self, force=True):
if force:
try:
while True:
@@ -135,7 +141,7 @@ class Worker(Thread):
self.__in.put(None)
#--- Helpers
# --- Helpers
@local_thread
def call_by_name(self, handler, function_name, *args, **kwargs):

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2
#!/usr/bin/env python3
# -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,27 +29,26 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest
import worker
from worker import Worker, local_thread, local_thread_blocking
from Queue import Queue
from logging.handlers import BufferingHandler
from logging import ERROR
import logging
import unittest
from logging import ERROR
from logging.handlers import BufferingHandler
from queue import Queue
from threading import Event
from time import sleep
from worker import Worker, local_thread, local_thread_blocking
class WorkerTest(unittest.TestCase):
def setUp(self):
def set_ev(fu):
def new_fu(*args, **kwargs):
s = args[0]
s.event.set()
s.val = (args, kwargs)
return fu(*args, **kwargs)
return new_fu
class ATestWorker(Worker):
@@ -91,7 +90,6 @@ class WorkerTest(unittest.TestCase):
def call_me_by_name_blocking(self, arg1, arg2):
return arg1, arg2
self.buha = BufferingHandler(10000)
q = Queue()
@@ -112,10 +110,10 @@ class WorkerTest(unittest.TestCase):
self.assertTrue(self.w.started)
def testName(self):
assert(self.w.name() == "Test")
assert (self.w.name() == "Test")
def testMessageQueue(self):
assert(self.w.message_queue() == self.q)
assert (self.w.message_queue() == self.q)
def testLocalThread(self):
s = "Testing"
@@ -124,14 +122,14 @@ class WorkerTest(unittest.TestCase):
self.w.event.wait(5)
args, kwargs = self.w.val
assert(args[1] == s)
assert (args[1] == s)
def testLocalThreadException(self):
self.buha.flush()
self.w.raise_(Exception())
sleep(0.1) # hard delay
assert(len(self.buha.buffer) != 0)
assert(self.buha.buffer[0].levelno == ERROR)
assert (len(self.buha.buffer) != 0)
assert (self.buha.buffer[0].levelno == ERROR)
def testCallByName(self):
self.w.event.clear()
@@ -139,31 +137,31 @@ class WorkerTest(unittest.TestCase):
self.w.event.wait(5)
args, kwargs = self.w.val
assert(args[1] == "arg1")
assert(kwargs["arg2"] == "arg2")
assert (args[1] == "arg1")
assert (kwargs["arg2"] == "arg2")
def testLocalThreadBlocking(self):
s = "Testing"
assert(s == self.w.echo_block(s))
assert (s == self.w.echo_block(s))
def testLocalThreadExceptionBlocking(self):
class TestException(Exception): pass
self.assertRaises(TestException, self.w.raise_blocking, TestException())
def testCallByNameBlocking(self):
arg1, arg2 = self.w.call_by_name_blocking(self.w, "call_me_by_name_blocking", "arg1", arg2="arg2")
assert(arg1 == "arg1")
assert(arg2 == "arg2")
assert (arg1 == "arg1")
assert (arg2 == "arg2")
def tearDown(self):
assert(self.w.stopped == False)
assert (self.w.stopped is False)
self.w.stop()
self.w.join(5)
assert(self.w.stopped == True)
assert self.w.stopped
if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName']
# import sys;sys.argv = ['', 'Test.testName']
unittest.main()