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 ## Requirements
mumo requires: mumo requires:
* python 2.7* * python >=3.2
* python-zeroc-ice * python-zeroc-ice
* murmur >=1.2.3* * murmur >=1.2.3*

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net> # 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 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import ConfigParser import configparser
import types import types
class Config(object): class Config(object):
""" """
Small abstraction for config loading 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 \ if (filename and not default) or \
(not filename and not default): return (not filename and not default): return
sections = set(default.iterkeys()) sections = set(default.keys())
if filename: if filename:
cfg = ConfigParser.RawConfigParser() cfg = configparser.RawConfigParser()
cfg.optionxform = str cfg.optionxform = str
with open(filename) as f: with open(filename) as f:
cfg.readfp(f) cfg.read_file(f)
sections.update(cfg.sections()) sections.update(cfg.sections())
for section in sections: for section in sections:
if type(section) == types.FunctionType: continue if isinstance(section, types.FunctionType):
continue
match = None match = None
for default_section in default.iterkeys(): for default_section in default.keys():
try: try:
if section == default_section or \ 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 match = default_section
break break
except ValueError: except ValueError:
continue continue
if match == None: if match is None:
continue continue
optiondefaults = default[match] optiondefaults = default[match]
@@ -74,7 +76,7 @@ class Config(object):
else: else:
try: try:
self.__dict__[section] = cfg.items(section) self.__dict__[section] = cfg.items(section)
except ConfigParser.NoSectionError: except configparser.NoSectionError:
self.__dict__[section] = [] self.__dict__[section] = []
else: else:
self.__dict__[section] = Config() self.__dict__[section] = Config()
@@ -84,42 +86,46 @@ class Config(object):
else: else:
try: try:
self.__dict__[section].__dict__[name] = conv(cfg.get(section, name)) 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 self.__dict__[section].__dict__[name] = vdefault
def __getitem__(self, key): def __getitem__(self, key):
return self.__dict__.__getitem__(key) return self.__dict__.__getitem__(key)
def __contains__(self, key): def __contains__(self, key):
return self.__dict__.__contains__(key) return self.__dict__.__contains__(key)
def x2bool(s): def x2bool(s):
""" """
Helper function to convert strings from the config to bool Helper function to convert strings from the config to bool
""" """
if isinstance(s, bool): if isinstance(s, bool):
return s return s
elif isinstance(s, basestring): elif isinstance(s, str):
return s.strip().lower() in ['1', 'true'] return s.strip().lower() in ['1', 'true']
raise ValueError() raise ValueError()
def commaSeperatedIntegers(s): def commaSeperatedIntegers(s):
""" """
Helper function to convert a string from the config Helper function to convert a string from the config
containing comma seperated integers into a list of integers containing comma seperated integers into a list of integers
""" """
return map(int, s.split(',')) return list(map(int, s.split(',')))
def commaSeperatedStrings(s): def commaSeperatedStrings(s):
""" """
Helper function to convert a string from the config Helper function to convert a string from the config
containing comma seperated strings into a list of strings 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): def commaSeperatedBool(s):
""" """
Helper function to convert a string from the config Helper function to convert a string from the config
containing comma seperated strings into a list of booleans 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 # -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net> # 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 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # 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 os
import re 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. Creates a temp file filled with 'content' and returns its path.
The file has to be manually deleted later on The file has to be manually deleted later on
@@ -43,11 +45,12 @@ def create_file(content = None):
fd, path = mkstemp() fd, path = mkstemp()
f = os.fdopen(fd, "wb") f = os.fdopen(fd, "wb")
if content: if content:
f.write(content) f.write(content.encode())
f.flush() f.flush()
f.close() f.close()
return path return path
class ConfigTest(unittest.TestCase): class ConfigTest(unittest.TestCase):
cfg_content = """[world] cfg_content = """[world]
domination = True domination = True
@@ -62,15 +65,15 @@ value = False
[Server_2] [Server_2]
value = True value = True
""" """
cfg_default = {'world':(('domination', x2bool, False), cfg_default = {'world': (('domination', x2bool, False),
('somestr', str, "fail"), ('somestr', str, "fail"),
('somenum', int, 0), ('somenum', int, 0),
('somenumtest', int, 1), ('somenumtest', int, 1),
('blubber', str, "empty"), ('blubber', str, "empty"),
('serverregex', re.compile, '.*')), ('serverregex', re.compile, '.*')),
(lambda x: re.match("Server_\d+",x)):(('value', x2bool, True),), (lambda x: re.match("Server_\d+", x)): (('value', x2bool, True),),
'somethingelse':(('bla', str, "test"),)} 'somethingelse': (('bla', str, "test"),)}
def setUp(self): def setUp(self):
pass pass
@@ -78,76 +81,75 @@ value = True
def tearDown(self): def tearDown(self):
pass pass
def testEmpty(self): def testEmpty(self):
path = create_file() path = create_file()
try: try:
cfg = Config(path, self.cfg_default) cfg = Config(path, self.cfg_default)
assert(cfg.world.domination == False) assert (cfg.world.domination == False)
assert(cfg.world.somestr == "fail") assert (cfg.world.somestr == "fail")
assert(cfg.world.somenum == 0) assert (cfg.world.somenum == 0)
self.assertRaises(AttributeError, getattr, cfg.world, "testfallbacknum") self.assertRaises(AttributeError, getattr, cfg.world, "testfallbacknum")
assert(cfg.somethingelse.bla == "test") assert (cfg.somethingelse.bla == "test")
finally: finally:
os.remove(path) os.remove(path)
def testX2bool(self): def testX2bool(self):
assert(x2bool(" true") == True) assert (x2bool(" true") == True)
assert(x2bool("false") == False) assert (x2bool("false") == False)
assert(x2bool(" TrUe") == True) assert (x2bool(" TrUe") == True)
assert(x2bool("FaLsE ") == False) assert (x2bool("FaLsE ") == False)
assert(x2bool("0 ") == False) assert (x2bool("0 ") == False)
assert(x2bool("1") == True) assert (x2bool("1") == True)
assert(x2bool(" 10") == False) assert (x2bool(" 10") == False)
assert(x2bool("notabool") == False) assert (x2bool("notabool") == False)
def testCommaSeperatedIntegers(self): 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") self.assertRaises(ValueError, commaSeperatedIntegers, "1,2,a")
def testCommaSeperatedStrings(self): def testCommaSeperatedStrings(self):
assert(commaSeperatedStrings("Bernd, the, bred !") == ["Bernd", "the", "bred !"]) assert (commaSeperatedStrings("Bernd, the, bred !") == ["Bernd", "the", "bred !"])
def testCommaSeperatedBool(self): 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): def testConfig(self):
path = create_file(self.cfg_content) path = create_file(self.cfg_content)
try: try:
try: try:
cfg = Config(path, self.cfg_default) cfg = Config(path, self.cfg_default)
except Exception, e: except Exception as e:
print e print(e)
assert(cfg.world.domination == True) assert (cfg.world.domination == True)
assert(cfg.world.somestr == "Blabla") assert (cfg.world.somestr == "Blabla")
assert(cfg.world.somenum == 10) assert (cfg.world.somenum == 10)
self.assertRaises(AttributeError, getattr, cfg.world, "testfallbacknum") self.assertRaises(AttributeError, getattr, cfg.world, "testfallbacknum")
self.assertEqual(cfg.world.blubber, "Things %(doesnotexistsasdefault)s") self.assertEqual(cfg.world.blubber, "Things %(doesnotexistsasdefault)s")
self.assertEqual(cfg.world.serverregex, re.compile("^\[[\w\d\-\(\):]{1,20}\]$")) self.assertEqual(cfg.world.serverregex, re.compile("^\[[\w\d\-\(\):]{1,20}\]$"))
assert(cfg.somethingelse.bla == "test") assert (cfg.somethingelse.bla == "test")
assert(cfg.Server_10.value == False) assert (cfg.Server_10.value == False)
assert(cfg.Server_2.value == True) assert (cfg.Server_2.value == True)
assert(cfg.Server_9.value == True) assert (cfg.Server_9.value == True)
finally: finally:
os.remove(path) os.remove(path)
def testLoadDefault(self): def testLoadDefault(self):
cfg = Config(default=self.cfg_default) cfg = Config(default=self.cfg_default)
assert(cfg.world.domination == False) assert (cfg.world.domination == False)
assert(cfg.somethingelse.bla == "test") assert (cfg.somethingelse.bla == "test")
assert(cfg.world.somenum == 0) assert (cfg.world.somenum == 0)
def testGetItem(self): def testGetItem(self):
cfg = Config(default=self.cfg_default) cfg = Config(default=self.cfg_default)
assert(cfg["world"]["domination"] == False) assert (cfg["world"]["domination"] == False)
assert("world" in cfg) assert ("world" in cfg)
def invalidaccess(c): def invalidaccess(c):
c["nointhisconfig"] c["nointhisconfig"]
self.assertRaises(KeyError, invalidaccess, cfg) self.assertRaises(KeyError, invalidaccess, cfg)
if __name__ == "__main__": if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName'] # import sys;sys.argv = ['', 'Test.testName']
unittest.main() unittest.main()

View File

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

View File

@@ -1,2 +1,2 @@
# No real module, just here to keep pydev and its # No real module, just here to keep pydev and its
# test runner happy. # test runner happy.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -35,77 +35,75 @@
# gamestate reported by Mumble positional audio plugins # gamestate reported by Mumble positional audio plugins
# #
from mumo_module import (MumoModule, import json
x2bool)
import re import re
try:
import json from config import x2bool
except ImportError: # Fallback for python < 2.6 from mumo_module import MumoModule
import simplejson as json
class bf2(MumoModule): class bf2(MumoModule):
default_config = {'bf2':( default_config = {'bf2': (
('gamecount', int, 1), ('gamecount', int, 1),
), ),
lambda x: re.match('g\d+', x):( lambda x: re.match('g\d+', x): (
('name', str, ''), ('name', str, ''),
('mumble_server', int, 1), ('mumble_server', int, 1),
('ipport_filter_negate', x2bool, False), ('ipport_filter_negate', x2bool, False),
('ipport_filter', re.compile, re.compile('.*')), ('ipport_filter', re.compile, re.compile('.*')),
('base', int, 0), ('base', int, 0),
('left', int, -1), ('left', int, -1),
('blufor', int, -1), ('blufor', int, -1),
('blufor_commander', int, -1), ('blufor_commander', int, -1),
('blufor_no_squad', int, -1), ('blufor_no_squad', int, -1),
('blufor_first_squad', int, -1), ('blufor_first_squad', int, -1),
('blufor_first_squad_leader', int, -1), ('blufor_first_squad_leader', int, -1),
('blufor_second_squad', int, -1), ('blufor_second_squad', int, -1),
('blufor_second_squad_leader', int, -1), ('blufor_second_squad_leader', int, -1),
('blufor_third_squad', int, -1), ('blufor_third_squad', int, -1),
('blufor_third_squad_leader', int, -1), ('blufor_third_squad_leader', int, -1),
('blufor_fourth_squad', int, -1), ('blufor_fourth_squad', int, -1),
('blufor_fourth_squad_leader', int, -1), ('blufor_fourth_squad_leader', int, -1),
('blufor_fifth_squad', int, -1), ('blufor_fifth_squad', int, -1),
('blufor_fifth_squad_leader', int, -1), ('blufor_fifth_squad_leader', int, -1),
('blufor_sixth_squad', int, -1), ('blufor_sixth_squad', int, -1),
('blufor_sixth_squad_leader', int, -1), ('blufor_sixth_squad_leader', int, -1),
('blufor_seventh_squad', int, -1), ('blufor_seventh_squad', int, -1),
('blufor_seventh_squad_leader', int, -1), ('blufor_seventh_squad_leader', int, -1),
('blufor_eighth_squad', int, -1), ('blufor_eighth_squad', int, -1),
('blufor_eighth_squad_leader', int, -1), ('blufor_eighth_squad_leader', int, -1),
('blufor_ninth_squad', int, -1), ('blufor_ninth_squad', int, -1),
('blufor_ninth_squad_leader', int, -1), ('blufor_ninth_squad_leader', int, -1),
('opfor', int, -1), ('opfor', int, -1),
('opfor_commander', int, -1), ('opfor_commander', int, -1),
('opfor_no_squad', int, -1), ('opfor_no_squad', int, -1),
('opfor_first_squad', int, -1), ('opfor_first_squad', int, -1),
('opfor_first_squad_leader', int, -1), ('opfor_first_squad_leader', int, -1),
('opfor_second_squad', int, -1), ('opfor_second_squad', int, -1),
('opfor_second_squad_leader', int, -1), ('opfor_second_squad_leader', int, -1),
('opfor_third_squad', int, -1), ('opfor_third_squad', int, -1),
('opfor_third_squad_leader', int, -1), ('opfor_third_squad_leader', int, -1),
('opfor_fourth_squad', int, -1), ('opfor_fourth_squad', int, -1),
('opfor_fourth_squad_leader', int, -1), ('opfor_fourth_squad_leader', int, -1),
('opfor_fifth_squad', int, -1), ('opfor_fifth_squad', int, -1),
('opfor_fifth_squad_leader', int, -1), ('opfor_fifth_squad_leader', int, -1),
('opfor_sixth_squad', int, -1), ('opfor_sixth_squad', int, -1),
('opfor_sixth_squad_leader', int, -1), ('opfor_sixth_squad_leader', int, -1),
('opfor_seventh_squad', int, -1), ('opfor_seventh_squad', int, -1),
('opfor_seventh_squad_leader', int, -1), ('opfor_seventh_squad_leader', int, -1),
('opfor_eighth_squad', int, -1), ('opfor_eighth_squad', int, -1),
('opfor_eighth_squad_leader', int, -1), ('opfor_eighth_squad_leader', int, -1),
('opfor_ninth_squad', int, -1), ('opfor_ninth_squad', int, -1),
('opfor_ninth_squad_leader', int, -1) ('opfor_ninth_squad_leader', int, -1)
), ),
} }
id_to_squad_name = ["no", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth"] 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) MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule() self.murmur = manager.getMurmurModule()
@@ -114,7 +112,7 @@ class bf2(MumoModule):
manager = self.manager() manager = self.manager()
log = self.log() log = self.log()
log.debug("Register for Server callbacks") log.debug("Register for Server callbacks")
servers = set() servers = set()
for i in range(cfg.bf2.gamecount): for i in range(cfg.bf2.gamecount):
try: try:
@@ -122,23 +120,24 @@ class bf2(MumoModule):
except KeyError: except KeyError:
log.error("Invalid configuration. Game configuration for 'g%d' not found.", i) log.error("Invalid configuration. Game configuration for 'g%d' not found.", i)
return return
self.sessions = {} # {serverid:{sessionid:laststate}} self.sessions = {} # {serverid:{sessionid:laststate}}
manager.subscribeServerCallbacks(self, servers) manager.subscribeServerCallbacks(self, servers)
manager.subscribeMetaCallbacks(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): def update_state(self, server, oldstate, newstate):
log = self.log() log = self.log()
sid = server.id() sid = server.id()
session = newstate.session session = newstate.session
newoldchannel = newstate.channel newoldchannel = newstate.channel
try: try:
opc = oldstate.parsedcontext opc = oldstate.parsedcontext
ogcfgname = opc["gamename"] ogcfgname = opc["gamename"]
@@ -147,15 +146,15 @@ class bf2(MumoModule):
opi = oldstate.parsedidentity opi = oldstate.parsedidentity
except (AttributeError, KeyError): except (AttributeError, KeyError):
og = None og = None
opi = {} opi = {}
opc = {} opc = {}
if oldstate and oldstate.is_linked: if oldstate and oldstate.is_linked:
oli = True oli = True
else: else:
oli = False oli = False
try: try:
npc = newstate.parsedcontext npc = newstate.parsedcontext
ngcfgname = npc["gamename"] ngcfgname = npc["gamename"]
@@ -164,23 +163,25 @@ class bf2(MumoModule):
npi = newstate.parsedidentity npi = newstate.parsedidentity
except (AttributeError, KeyError): except (AttributeError, KeyError):
ng = None ng = None
npi = {} npi = {}
npc = {} npc = {}
nli = False nli = False
if newstate and newstate.is_linked: if newstate and newstate.is_linked:
nli = True nli = True
else: else:
nli = False nli = False
if not oli and nli: 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") server.addUserToGroup(0, session, "bf2_linked")
if opi and opc: if opi and opc:
squadname = self.id_to_squad_name[opi["squad"]] 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["base"], session, "bf2_%s_game" % (og or ogcfgname))
server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_commander") server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_commander")
server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_squad_leader") server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_squad_leader")
@@ -189,72 +190,74 @@ class bf2(MumoModule):
server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_team") server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_team")
channame = "left" channame = "left"
newstate.channel = ogcfg["left"] newstate.channel = ogcfg["left"]
if npc and npi: 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"]] squadname = self.id_to_squad_name[npi["squad"]]
# Add to game group # Add to game group
location = "base" location = "base"
group = "bf2_%s_game" % (ng or ngcfgname) group = "bf2_%s_game" % (ng or ngcfgname)
server.addUserToGroup(ngcfg[location], session, group) server.addUserToGroup(ngcfg[location], session, group)
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location) log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
# Then add to team group # Then add to team group
location = npi["team"] location = npi["team"]
group = "bf2_team" group = "bf2_team"
server.addUserToGroup(ngcfg[location], session, group) server.addUserToGroup(ngcfg[location], session, group)
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location) log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
# Then add to squad group # Then add to squad group
group = "bf2_%s_squad" % squadname group = "bf2_%s_squad" % squadname
server.addUserToGroup(ngcfg[location], session, group) server.addUserToGroup(ngcfg[location], session, group)
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location) log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
channame = "%s_%s_squad" % (npi["team"], self.id_to_squad_name[npi["squad"]]) channame = "%s_%s_squad" % (npi["team"], self.id_to_squad_name[npi["squad"]])
newstate.channel = ngcfg[channame] newstate.channel = ngcfg[channame]
if npi["squad_leader"]: if npi["squad_leader"]:
# In case the leader flag is set add to leader group # In case the leader flag is set add to leader group
group = "bf2_%s_squad_leader" % squadname group = "bf2_%s_squad_leader" % squadname
server.addUserToGroup(ngcfg[location], session, group) server.addUserToGroup(ngcfg[location], session, group)
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location) log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
group = "bf2_squad_leader" group = "bf2_squad_leader"
server.addUserToGroup(ngcfg[location], session, group) server.addUserToGroup(ngcfg[location], session, group)
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location) log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
# Override previous moves # Override previous moves
channame = "%s_%s_squad_leader" % (npi["team"], self.id_to_squad_name[npi["squad"]]) channame = "%s_%s_squad_leader" % (npi["team"], self.id_to_squad_name[npi["squad"]])
newstate.channel = ngcfg[channame] newstate.channel = ngcfg[channame]
if npi["commander"]: if npi["commander"]:
group = "bf2_commander" group = "bf2_commander"
server.addUserToGroup(ngcfg[location], session, group) server.addUserToGroup(ngcfg[location], session, group)
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location) log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
# Override previous moves # Override previous moves
channame = "%s_commander" % npi["team"] channame = "%s_commander" % npi["team"]
newstate.channel = ngcfg[channame] newstate.channel = ngcfg[channame]
if oli and not nli: 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") server.removeUserFromGroup(0, session, "bf2_linked")
if newstate.channel >= 0 and newoldchannel != newstate.channel: if 0 <= newstate.channel != newoldchannel:
if ng == None: if ng is None:
log.debug("Moving '%s' leaving %s to channel %s", newstate.name, og or ogcfgname, channame) log.debug("Moving '%s' leaving %s to channel %s", newstate.name, og or ogcfgname, channame)
else: else:
log.debug("Moving '%s' @ %s to channel %s", newstate.name, ng or ngcfgname, channame) log.debug("Moving '%s' @ %s to channel %s", newstate.name, ng or ngcfgname, channame)
server.setState(newstate) server.setState(newstate)
def handle(self, server, state): def handle(self, server, state):
def verify(mdict, key, vtype): def verify(mdict, key, vtype):
if not isinstance(mdict[key], vtype): if not isinstance(mdict[key], vtype):
raise ValueError("'%s' of invalid type" % key) raise ValueError("'%s' of invalid type" % key)
cfg = self.cfg() cfg = self.cfg()
log = self.log() log = self.log()
sid = server.id() sid = server.id()
@@ -263,17 +266,17 @@ class bf2(MumoModule):
state.parsedidentity = {} state.parsedidentity = {}
state.parsedcontext = {} state.parsedcontext = {}
state.is_linked = False state.is_linked = False
if sid not in self.sessions: # Make sure there is a dict to store states in if sid not in self.sessions: # Make sure there is a dict to store states in
self.sessions[sid] = {} self.sessions[sid] = {}
update = False update = False
if state.session in self.sessions[sid]: if state.session in self.sessions[sid]:
if state.identity != self.sessions[sid][state.session].identity or \ if state.identity != self.sessions[sid][state.session].identity or \
state.context != self.sessions[sid][state.session].context: state.context != self.sessions[sid][state.session].context:
# identity or context changed => update # identity or context changed => update
update = True update = True
else: # id and context didn't change hence the old data must still be valid else: # id and context didn't change hence the old data must still be valid
state.is_linked = self.sessions[sid][state.session].is_linked state.is_linked = self.sessions[sid][state.session].is_linked
state.parsedcontext = self.sessions[sid][state.session].parsedcontext state.parsedcontext = self.sessions[sid][state.session].parsedcontext
state.parsedidentity = self.sessions[sid][state.session].parsedidentity state.parsedidentity = self.sessions[sid][state.session].parsedidentity
@@ -282,46 +285,48 @@ class bf2(MumoModule):
# New user with engaged plugin => update # New user with engaged plugin => update
self.sessions[sid][state.session] = None self.sessions[sid][state.session] = None
update = True update = True
if not update: if not update:
self.sessions[sid][state.session] = state self.sessions[sid][state.session] = state
return return
# The plugin will always prefix "Battlefield 2\0" to the context for the bf2 PA plugin # The plugin will always prefix "Battlefield 2\0" to the context for the bf2 PA plugin
# don't bother analyzing anything if it isn't there # don't bother analyzing anything if it isn't there
splitcontext = state.context.split('\0', 1) splitcontext = state.context.split('\0', 1)
if splitcontext[0] == "Battlefield 2": if splitcontext[0] == "Battlefield 2":
state.is_linked = True state.is_linked = True
if state.identity and len(splitcontext) == 1: if state.identity and len(splitcontext) == 1:
#LEGACY: Assume broken Ice 3.2 which doesn't transmit context after \0 # 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 ;-) 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: if state.is_linked and len(splitcontext) == 2 and state.identity:
try: try:
context = json.loads(splitcontext[1]) context = json.loads(splitcontext[1])
verify(context, "ipport", basestring) verify(context, "ipport", str)
for i in range(cfg.bf2.gamecount): for i in range(cfg.bf2.gamecount):
# Try to find a matching game # Try to find a matching game
gamename = "g%d" % i gamename = "g%d" % i
gamecfg = getattr(cfg, gamename) gamecfg = getattr(cfg, gamename)
if gamecfg.mumble_server == server.id(): 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: if not_matched == gamecfg.ipport_filter_negate:
break break
gamename = None gamename = None
if not gamename: if not gamename:
raise ValueError("No matching game found") raise ValueError("No matching game found")
context["gamecfg"] = gamecfg context["gamecfg"] = gamecfg
context["gamename"] = gamename context["gamename"] = gamename
state.parsedcontext = context state.parsedcontext = context
except (ValueError, KeyError, AttributeError), 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)) log.debug("Invalid context for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid,
sid, repr(e))
try: try:
identity = json.loads(state.identity) identity = json.loads(state.identity)
verify(identity, "commander", bool) verify(identity, "commander", bool)
@@ -329,48 +334,57 @@ class bf2(MumoModule):
verify(identity, "squad", int) verify(identity, "squad", int)
if identity["squad"] < 0 or identity["squad"] > 9: if identity["squad"] < 0 or identity["squad"] > 9:
raise ValueError("Invalid squad number") raise ValueError("Invalid squad number")
verify(identity, "team", basestring) verify(identity, "team", str)
if identity["team"] != "opfor" and identity["team"] != "blufor": if identity["team"] != "opfor" and identity["team"] != "blufor":
raise ValueError("Invalid team identified") 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"]) identity["team"] = str(identity["team"])
state.parsedidentity = identity state.parsedidentity = identity
except (KeyError, ValueError), 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)) 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 # Update state and remember it
self.update_state(server, self.sessions[sid][state.session], state) self.update_state(server, self.sessions[sid][state.session], state)
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: try:
sid = server.id() sid = server.id()
del self.sessions[sid][state.session] 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) self.handle(server, state)
def userConnected(self, server, state, context = None): def userConnected(self, server, state, context=None):
self.handle(server, state) self.handle(server, state)
def userTextMessage(self, server, user, message, current=None): pass def userTextMessage(self, server, user, message, current=None):
def channelCreated(self, server, state, context = None): pass 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
# #
#--- Meta callback functions # --- Meta callback functions
# #
def started(self, server, context = None): def started(self, server, context=None):
self.sessions[server.id()] = {} self.sessions[server.id()] = {}
def stopped(self, server, context = None): def stopped(self, server, context=None):
self.sessions[server.id()] = {} self.sessions[server.id()] = {}

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -37,45 +37,41 @@
# once they become active again # once they become active again
# #
from mumo_module import (commaSeperatedIntegers,
commaSeperatedBool,
commaSeperatedStrings,
MumoModule)
from threading import Timer
import re import re
from threading import Timer
from config import commaSeperatedIntegers, commaSeperatedBool, commaSeperatedStrings
from mumo_module import MumoModule
class idlemove(MumoModule): class idlemove(MumoModule):
default_config = {'idlemove':( default_config = {'idlemove': (
('interval', float, 0.1), ('interval', float, 0.1),
('servers', commaSeperatedIntegers, []), ('servers', commaSeperatedIntegers, []),
), ),
lambda x: re.match('(all)|(server_\d+)', x):( lambda x: re.match('(all)|(server_\d+)', x): (
('threshold', commaSeperatedIntegers, [3600]), ['threshold', commaSeperatedIntegers, [3600]],
('mute', commaSeperatedBool, [True]), ('mute', commaSeperatedBool, [True]),
('deafen', commaSeperatedBool, [False]), ('deafen', commaSeperatedBool, [False]),
('channel', commaSeperatedIntegers, [1]), ('channel', commaSeperatedIntegers, [1]),
('source_channel', commaSeperatedIntegers, [-1]), ('source_channel', commaSeperatedIntegers, [-1]),
('whitelist', commaSeperatedStrings, []), ('whitelist', commaSeperatedStrings, []),
('channel_whitelist', commaSeperatedIntegers, []) ('channel_whitelist', commaSeperatedIntegers, [])
), ),
} }
def __init__(self, name, manager, configuration=None): def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration) MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule() self.murmur = manager.getMurmurModule()
self.watchdog = None self.watchdog = None
def connected(self): def connected(self):
self.affectedusers = {} # {serverid:set(sessionids,...)} self.affectedusers = {} # {serverid:set(sessionids,...)}
manager = self.manager() manager = self.manager()
log = self.log() log = self.log()
log.debug("Register for Meta & Server callbacks") log.debug("Register for Meta & Server callbacks")
cfg = self.cfg() cfg = self.cfg()
servers = cfg.idlemove.servers servers = cfg.idlemove.servers
if not servers: if not servers:
@@ -83,11 +79,11 @@ class idlemove(MumoModule):
manager.subscribeServerCallbacks(self, servers) manager.subscribeServerCallbacks(self, servers)
manager.subscribeMetaCallbacks(self, servers) manager.subscribeMetaCallbacks(self, servers)
if not self.watchdog: if not self.watchdog:
self.watchdog = Timer(cfg.idlemove.interval, self.handleIdleMove) self.watchdog = Timer(cfg.idlemove.interval, self.handleIdleMove)
self.watchdog.start() self.watchdog.start()
def disconnected(self): def disconnected(self):
self.affectedusers = {} self.affectedusers = {}
if self.watchdog: if self.watchdog:
@@ -98,58 +94,55 @@ class idlemove(MumoModule):
cfg = self.cfg() cfg = self.cfg()
try: try:
meta = self.manager().getMeta() meta = self.manager().getMeta()
if not cfg.idlemove.servers: if not cfg.idlemove.servers:
servers = meta.getBootedServers() servers = meta.getBootedServers()
else: else:
servers = [meta.getServer(server) for server in cfg.idlemove.servers] servers = [meta.getServer(server) for server in cfg.idlemove.servers]
for server in servers: for server in servers:
if not server: continue
if server: if server:
for user in server.getUsers().itervalues(): for user in server.getUsers().values():
self.UpdateUserAutoAway(server, user) self.UpdateUserAutoAway(server, user)
finally: finally:
# Renew the timer # Renew the timer
self.watchdog = Timer(cfg.idlemove.interval, self.handleIdleMove) self.watchdog = Timer(cfg.idlemove.interval, self.handleIdleMove)
self.watchdog.start() self.watchdog.start()
def UpdateUserAutoAway(self, server, user): def UpdateUserAutoAway(self, server, user):
log = self.log() log = self.log()
sid = server.id() sid = server.id()
try: try:
scfg = getattr(self.cfg(), 'server_%d' % sid) scfg = getattr(self.cfg(), 'server_%d' % sid)
except AttributeError: except AttributeError:
scfg = self.cfg().all scfg = self.cfg().all
try: try:
index = self.affectedusers[sid] index = self.affectedusers[sid]
except KeyError: except KeyError:
self.affectedusers[sid] = set() self.affectedusers[sid] = set()
index = self.affectedusers[sid] index = self.affectedusers[sid]
# Check if the user is whitelisted # Check if the user is whitelisted
if user.name in scfg.whitelist: if user.name in scfg.whitelist:
return return
# Remember values so we can see changes later # Remember values so we can see changes later
threshold = None
mute = user.mute mute = user.mute
deafen = user.deaf deafen = user.deaf
channel = user.channel channel = user.channel
update = False update = False
over_threshold = False over_threshold = False
# Search all our stages top down for a violated treshold and pick the first # Search all our stages top down for a violated treshold and pick the first
for i in range(len(scfg.threshold) - 1, -1, -1): for i in range(len(scfg.threshold) - 1, -1, -1):
try: try:
source_channel = scfg.source_channel[i] source_channel = scfg.source_channel[i]
except IndexError: except IndexError:
source_channel = -1 source_channel = -1
try: try:
threshold = scfg.threshold[i] threshold = scfg.threshold[i]
mute = scfg.mute[i] mute = scfg.mute[i]
@@ -159,28 +152,27 @@ class idlemove(MumoModule):
log.warning("Incomplete configuration for stage %d of server %i, ignored", i, server.id()) log.warning("Incomplete configuration for stage %d of server %i, ignored", i, server.id())
continue continue
if user.idlesecs > threshold and\ if user.idlesecs > threshold and user.channel not in scfg.channel_whitelist and (
user.channel not in scfg.channel_whitelist and\ source_channel == -1 or user.channel == source_channel or user.channel == channel):
(source_channel == -1 or\
user.channel == source_channel or\
user.channel == channel):
over_threshold = True over_threshold = True
# Update if state changes needed # Update if state changes needed
if user.deaf != deafen: if user.deaf != deafen:
update = True update = True
if user.mute != mute: if user.mute != mute:
update = True update = True
if channel >= 0 and user.channel != channel: if 0 <= channel != user.channel:
update = True update = True
if update: if update:
index.add(user.session) 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', log.info(
user.idlesecs, threshold, user.name, user.session, user.userid, user.mute, mute, user.deaf, deafen, '%ds > %ds: State transition for user %s (%d/%d) from mute %s -> %s / deaf %s -> %s | channel %d -> %d on server %d',
user.channel, channel, server.id()) user.idlesecs, threshold, user.name, user.session, user.userid, user.mute, mute, user.deaf,
deafen,
user.channel, channel, server.id())
break break
if not over_threshold and user.session in self.affectedusers[sid]: if not over_threshold and user.session in self.affectedusers[sid]:
deafen = False deafen = False
mute = False mute = False
@@ -188,15 +180,15 @@ class idlemove(MumoModule):
index.remove(user.session) index.remove(user.session)
log.info("Restore user %s (%d/%d) on server %d", user.name, user.session, user.userid, server.id()) log.info("Restore user %s (%d/%d) on server %d", user.name, user.session, user.userid, server.id())
update = True update = True
if update: if update:
user.deaf = deafen user.deaf = deafen
user.mute = mute user.mute = mute
user.channel = channel user.channel = channel
server.setState(user) server.setState(user)
# #
#--- Server callback functions # --- Server callback functions
# #
def userDisconnected(self, server, state, context=None): def userDisconnected(self, server, state, context=None):
try: try:
@@ -205,27 +197,35 @@ class idlemove(MumoModule):
index.remove(state.session) index.remove(state.session)
except KeyError: except KeyError:
pass pass
def userStateChanged(self, server, state, context=None): def userStateChanged(self, server, state, context=None):
self.UpdateUserAutoAway(server, state) self.UpdateUserAutoAway(server, state)
def userConnected(self, server, state, context=None): pass # Unused callbacks def userConnected(self, server, state, context=None):
def userTextMessage(self, server, user, message, current=None): pass pass # Unused callbacks
def channelCreated(self, server, state, context=None): pass
def channelRemoved(self, server, state, context=None): pass def userTextMessage(self, server, user, message, current=None):
def channelStateChanged(self, server, state, context=None): pass 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() sid = server.id()
self.affectedusers[sid] = set() self.affectedusers[sid] = set()
self.log().debug('Handling server %d', sid) self.log().debug('Handling server %d', sid)
def stopped(self, server, context = None): def stopped(self, server, context=None):
sid = server.id() sid = server.id()
self.affectedusers[sid] = set() self.affectedusers[sid] = set()
self.log().debug('Server %d gone', sid) self.log().debug('Server %d gone', sid)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net> # 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. # they connect regardless of which channel they were in when they left.
# #
from mumo_module import (commaSeperatedIntegers,
MumoModule)
import re import re
from config import commaSeperatedIntegers
from mumo_module import MumoModule
class onjoin(MumoModule): class onjoin(MumoModule):
default_config = {'onjoin':( default_config = {'onjoin': (
('servers', commaSeperatedIntegers, []), ('servers', commaSeperatedIntegers, []),
), ),
'all':( 'all': (
('channel', int, 1), ('channel', int, 1),
), ),
lambda x: re.match('server_\d+', x):( lambda x: re.match('server_\d+', x): (
('channel', int, 1), ('channel', int, 1),
) )
} }
def __init__(self, name, manager, configuration = None): def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration) MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule() self.murmur = manager.getMurmurModule()
@@ -60,39 +61,53 @@ class onjoin(MumoModule):
manager = self.manager() manager = self.manager()
log = self.log() log = self.log()
log.debug("Register for Server callbacks") log.debug("Register for Server callbacks")
servers = self.cfg().onjoin.servers servers = self.cfg().onjoin.servers
if not servers: if not servers:
servers = manager.SERVERS_ALL servers = manager.SERVERS_ALL
manager.subscribeServerCallbacks(self, servers) 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() log = self.log()
sid = server.id() sid = server.id()
try: try:
scfg = getattr(self.cfg(), 'server_%d' % sid) scfg = getattr(self.cfg(), 'server_%d' % sid)
except AttributeError: except AttributeError:
scfg = self.cfg().all scfg = self.cfg().all
if state.channel != scfg.channel: 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 state.channel = scfg.channel
try: try:
server.setState(state) server.setState(state)
except self.murmur.InvalidChannelException: 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 userDisconnected(self, server, state, context=None):
def userTextMessage(self, server, user, message, current=None): pass pass
def channelCreated(self, server, state, context = None): pass
def channelRemoved(self, server, state, context = None): pass def userStateChanged(self, server, state, context=None):
def channelStateChanged(self, server, state, context = None): pass 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 # -*- coding: utf-8
# Copyright (C) 2015 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2015 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -35,17 +35,19 @@
# entries to a user's context menu. # entries to a user's context menu.
# #
from mumo_module import (commaSeperatedIntegers,
MumoModule)
import cgi import cgi
from config import commaSeperatedIntegers
from mumo_module import MumoModule
class samplecontext(MumoModule): class samplecontext(MumoModule):
default_config = {'samplecontext':( default_config = {'samplecontext': (
('servers', commaSeperatedIntegers, []), ('servers', commaSeperatedIntegers, []),
), ),
} }
def __init__(self, name, manager, configuration = None): def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration) MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule() self.murmur = manager.getMurmurModule()
self.action_poke_user = manager.getUniqueAction() self.action_poke_user = manager.getUniqueAction()
@@ -56,19 +58,19 @@ class samplecontext(MumoModule):
manager = self.manager() manager = self.manager()
log = self.log() log = self.log()
log.debug("Register for Server callbacks") log.debug("Register for Server callbacks")
servers = self.cfg().samplecontext.servers servers = self.cfg().samplecontext.servers
if not servers: if not servers:
servers = manager.SERVERS_ALL servers = manager.SERVERS_ALL
manager.subscribeServerCallbacks(self, servers) manager.subscribeServerCallbacks(self, servers)
def disconnected(self): pass def disconnected(self): pass
# #
#--- Server callback functions # --- Server callback functions
# #
def __on_poke_user(self, server, action, user, target): def __on_poke_user(self, server, action, user, target):
assert action == self.action_poke_user assert action == self.action_poke_user
self.log().info(user.name + " poked " + target.name) self.log().info(user.name + " poked " + target.name)
@@ -78,7 +80,7 @@ class samplecontext(MumoModule):
assert action == self.action_info assert action == self.action_info
self.log().info(user.name + " wants info on " + str(target)); self.log().info(user.name + " wants info on " + str(target));
server.sendMessage(user.session, server.sendMessage(user.session,
"<small><pre>" + cgi.escape(str(target)) + "</pre></small>") "<small><pre>" + cgi.escape(str(target)) + "</pre></small>")
def __on_remove_this(self, server, action, user, target): def __on_remove_this(self, server, action, user, target):
# This will remove the entry identified by "action" from # This will remove the entry identified by "action" from
@@ -86,7 +88,7 @@ class samplecontext(MumoModule):
self.log().info(user.name + " triggered removal") self.log().info(user.name + " triggered removal")
self.manager().removeContextMenuEntry(server, action) 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 # Adding the entries here means if mumo starts up after users
# already connected they won't have the new entries before they # already connected they won't have the new entries before they
# reconnect. You can also use the "connected" callback to # reconnect. You can also use the "connected" callback to
@@ -97,36 +99,40 @@ class samplecontext(MumoModule):
manager = self.manager() manager = self.manager()
manager.addContextMenuEntry( manager.addContextMenuEntry(
server, # Server of user server, # Server of user
user, # User which should receive the new entry user, # User which should receive the new entry
self.action_poke_user, # Identifier for the action self.action_poke_user, # Identifier for the action
"Poke", # Text in the client "Poke", # Text in the client
self.__on_poke_user, # Callback called when user uses the entry self.__on_poke_user, # Callback called when user uses the entry
self.murmur.ContextUser # We only want to show this entry on users self.murmur.ContextUser # We only want to show this entry on users
) )
manager.addContextMenuEntry( manager.addContextMenuEntry(
server, server,
user, user,
self.action_info, self.action_info,
"Info", "Info",
self.__on_info, self.__on_info,
self.murmur.ContextUser | self.murmur.ContextChannel # Show for users and channels self.murmur.ContextUser | self.murmur.ContextChannel # Show for users and channels
) )
manager.addContextMenuEntry( manager.addContextMenuEntry(
server, server,
user, user,
self.action_remove, self.action_remove,
"Remove this entry from everyone", "Remove this entry from everyone",
self.__on_remove_this, self.__on_remove_this,
self.murmur.ContextUser | self.murmur.ContextChannel | self.murmur.ContextServer self.murmur.ContextUser | self.murmur.ContextChannel | self.murmur.ContextServer
) )
def userDisconnected(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 userTextMessage(self, server, user, message, current=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 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 # -*- coding: utf-8
# Copyright (C) 2011 Stefan Hacker <dd0t@users.sourceforge.net> # 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 # 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 datetime import timedelta
from config import commaSeperatedIntegers
from mumo_module import MumoModule
class seen(MumoModule): class seen(MumoModule):
default_config = {'seen':( default_config = {'seen': (
('servers', commaSeperatedIntegers, []), ('servers', commaSeperatedIntegers, []),
('keyword', str, '!seen') ('keyword', str, '!seen')
) )
} }
def __init__(self, name, manager, configuration = None): def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration) MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule() self.murmur = manager.getMurmurModule()
self.keyword = self.cfg().seen.keyword self.keyword = self.cfg().seen.keyword
@@ -55,31 +56,33 @@ class seen(MumoModule):
manager = self.manager() manager = self.manager()
log = self.log() log = self.log()
log.debug("Register for Server callbacks") log.debug("Register for Server callbacks")
servers = self.cfg().seen.servers servers = self.cfg().seen.servers
if not servers: if not servers:
servers = manager.SERVERS_ALL servers = manager.SERVERS_ALL
manager.subscribeServerCallbacks(self, servers) manager.subscribeServerCallbacks(self, servers)
def disconnected(self): pass def disconnected(self):
pass
def sendMessage(self, server, user, message, msg): def sendMessage(self, server, user, message, msg):
if message.channels: if message.channels:
server.sendMessageChannel(user.channel, False, msg) server.sendMessageChannel(user.channel, False, msg)
else: else:
server.sendMessage(user.session, msg) server.sendMessage(user.session, msg)
server.sendMessage(message.sessions[0], msg) server.sendMessage(message.sessions[0], msg)
# #
#--- Server callback functions # --- Server callback functions
# #
def userTextMessage(self, server, user, message, current=None): def userTextMessage(self, server, user, message, current=None):
if message.text.startswith(self.keyword) and \ if message.text.startswith(self.keyword) and \
(len(message.sessions) == 1 or (len(message.sessions) == 1 or
(len(message.channels) == 1 and \ (len(message.channels) == 1 and \
message.channels[0] == user.channel)): message.channels[0] == user.channel)):
tuname = message.text[len(self.keyword):].strip() tuname = message.text[len(self.keyword):].strip()
self.log().debug("User %s (%d|%d) on server %d asking for '%s'", self.log().debug("User %s (%d|%d) on server %d asking for '%s'",
user.name, user.session, user.userid, server.id(), tuname) user.name, user.session, user.userid, server.id(), tuname)
@@ -89,35 +92,43 @@ class seen(MumoModule):
msg = "User '%s' knows how to spell his name" % tuname msg = "User '%s' knows how to spell his name" % tuname
self.sendMessage(server, user, message, msg) self.sendMessage(server, user, message, msg)
return return
# Check online users # Check online users
for cuser in server.getUsers().itervalues(): for cuser in server.getUsers().values():
if tuname == cuser.name: if tuname == cuser.name:
msg = "User '%s' is currently online, has been idle for %s" % (tuname, msg = "User '%s' is currently online, has been idle for %s" % (tuname,
timedelta(seconds=cuser.idlesecs)) timedelta(seconds=cuser.idlesecs))
self.sendMessage(server, user, message, msg) self.sendMessage(server, user, message, msg)
return return
# Check registrations # Check registrations
for cuid, cuname in server.getRegisteredUsers(tuname).iteritems(): for cuid, cuname in server.getRegisteredUsers(tuname).items():
if cuname == tuname: if cuname == tuname:
ureg = server.getRegistration(cuid) ureg = server.getRegistration(cuid)
if ureg: if ureg:
msg = "User '%s' was last seen %s UTC" % (tuname, msg = "User '%s' was last seen %s UTC" % (tuname,
ureg[self.murmur.UserInfo.UserLastActive]) ureg[self.murmur.UserInfo.UserLastActive])
self.sendMessage(server, user, message, msg) self.sendMessage(server, user, message, msg)
return return
msg = "I don't know who user '%s' is" % tuname msg = "I don't know who user '%s' is" % tuname
self.sendMessage(server, user, message, msg) self.sendMessage(server, user, message, msg)
def userConnected(self, server, state, context=None):
pass
def userConnected(self, server, state, context = None): pass
def userDisconnected(self, server, state, context = None): pass def userDisconnected(self, server, state, context=None):
def userStateChanged(self, server, state, context = None): pass pass
def channelCreated(self, server, state, context = None): pass def userStateChanged(self, server, state, context=None):
def channelRemoved(self, server, state, context = None): pass 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 # -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -31,13 +31,14 @@
import sqlite3 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): class SourceDB(object):
NO_SERVER = "" NO_SERVER = ""
NO_TEAM = -1 NO_TEAM = -1
def __init__(self, path = ":memory:"): def __init__(self, path=":memory:"):
""" """
Initialize the sqlite database in the given path. If no path Initialize the sqlite database in the given path. If no path
is given the database is created in memory. is given the database is created in memory.
@@ -54,7 +55,7 @@ class SourceDB(object):
UNIQUE(sid, cid), UNIQUE(sid, cid),
PRIMARY KEY (sid, game, server, team) PRIMARY KEY (sid, game, server, team)
)""") )""")
self.db.execute(""" self.db.execute("""
CREATE TABLE IF NOT EXISTS mapped_names ( CREATE TABLE IF NOT EXISTS mapped_names (
sid INTEGER NOT NULL, sid INTEGER NOT NULL,
@@ -75,38 +76,39 @@ class SourceDB(object):
self.db.commit() self.db.commit()
self.db.close() self.db.close()
self.db = None self.db = None
def isOk(self): def isOk(self):
""" """
True if the database is correctly initialized 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 Returns the mapped name for the given parameters or default if no
mapping exists. mapping exists.
""" """
assert(sid != None and game != None) assert (sid is not None and game is not None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER)) 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 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 Stores a mapping for the given (sid, game, server, team) combination
to the given name. The mapping can then be retrieved with nameFor() in to the given name. The mapping can then be retrieved with nameFor() in
the future. the future.
""" """
assert(sid != None and game != None) assert (sid is not None and game is not None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER)) 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() 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 Returns the channel id for game specific channel. If only game
is passed the game root channel cid is returned. If additionally 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 If no channel matching the arguments has been registered with the database
before None is returned. before None is returned.
""" """
assert(sid != None and game != None) assert (sid is not None and game is not None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER)) 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 return v[0] if v else None
def channelForCid(self, sid, cid): def channelForCid(self, sid, cid):
@@ -126,44 +130,51 @@ class SourceDB(object):
Returns a tuple of (sid, cid, game, server, team) for the given cid. Returns a tuple of (sid, cid, game, server, team) for the given cid.
Returns None if the cid is unknown. Returns None if the cid is unknown.
""" """
assert(sid != None and cid != None) 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() return self.db.execute(
"SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and cid is ?",
def channelFor(self, sid, game, server = NO_SERVER, team = NO_TEAM): [sid, cid]).fetchone()
def channelFor(self, sid, game, server=NO_SERVER, team=NO_TEAM):
""" """
Returns matching channel as (sid, cid, game, server, team) tuple. Matching Returns matching channel as (sid, cid, game, server, team) tuple. Matching
behavior is the same as for cidFor() behavior is the same as for cidFor()
""" """
assert(sid != None and game != None) assert (sid is not None and game is not None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER)) 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 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. 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. If only the game is passed all server and team channels are matched.
This can be limited by passing server (and team). This can be limited by passing server (and team).
Returns empty list if no matches are found. Returns empty list if no matches are found.
""" """
assert(sid != None and game != None) assert (sid is not None and game is not None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER)) assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
suffix, params = self.__whereClauseForOptionals(server, team) 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,
def registerChannel(self, sid, cid, game, server = NO_SERVER, team = NO_TEAM): [sid, game] + params).fetchall()
def registerChannel(self, sid, cid, game, server=NO_SERVER, team=NO_TEAM):
""" """
Register a given channel with the database. Register a given channel with the database.
""" """
assert(sid != None and game != None) assert (sid is not None and game is not None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER)) 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() self.db.commit()
return True return True
def __whereClauseForOptionals(self, server, team): def __whereClauseForOptionals(self, server, team):
""" """
Generates where class conditions that interpret missing server Generates where class conditions that interpret missing server
@@ -171,49 +182,49 @@ class SourceDB(object):
Returns (suffix, additional parameters) tuple Returns (suffix, additional parameters) tuple
""" """
if server != self.NO_SERVER and team != self.NO_TEAM: 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: elif server != self.NO_SERVER:
return (" and server is ?", [server]) return " and server is ?", [server]
else: 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. Unregister a channel previously registered with the database.
""" """
assert(sid != None and game != None) assert (sid is not None and game is not None)
assert(not (team != self.NO_TEAM and server == self.NO_SERVER)) assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
suffix, params = self.__whereClauseForOptionals(server, team) suffix, params = self.__whereClauseForOptionals(server, team)
self.db.execute("DELETE FROM controlled_channels WHERE sid is ? and game is ?" + suffix, [sid, game] + params) self.db.execute("DELETE FROM controlled_channels WHERE sid is ? and game is ?" + suffix, [sid, game] + params)
self.db.commit() self.db.commit()
def dropChannel(self, sid, cid): def dropChannel(self, sid, cid):
""" """
Drops channel with given sid + cid 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.execute("DELETE FROM controlled_channels WHERE sid is ? and cid is ?", [sid, cid])
self.db.commit() self.db.commit()
def isRegisteredChannel(self, sid, cid): def isRegisteredChannel(self, sid, cid):
""" """
Returns true if a channel with given sid and cid is registered 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() 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): def registeredChannels(self):
""" """
Returns channels as a list of (sid, cid, game, server team) tuples grouped by sid Returns channels as a list of (sid, cid, game, server team) tuples grouped by sid
""" """
return self.db.execute("SELECT sid, cid, game, server, team FROM controlled_channels ORDER by sid").fetchall() return self.db.execute("SELECT sid, cid, game, server, team FROM controlled_channels ORDER by sid").fetchall()
def reset(self): def reset(self):
""" """
Deletes everything in the database Deletes everything in the database
@@ -221,6 +232,7 @@ class SourceDB(object):
self.db.execute("DELETE FROM mapped_names") self.db.execute("DELETE FROM mapped_names")
self.db.execute("DELETE FROM controlled_channels") self.db.execute("DELETE FROM controlled_channels")
self.db.commit() self.db.commit()
if __name__ == "__main__": if __name__ == "__main__":
pass pass

View File

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

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -35,16 +35,15 @@
# gamestate reported by Mumble positional audio plugins # 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 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): class source(MumoModule):
""" """
This class combines the basic mumble moderator callbacks with This class combines the basic mumble moderator callbacks with
@@ -52,30 +51,30 @@ class source(MumoModule):
context and identity information. context and identity information.
""" """
default_game_config = ( default_game_config = (
('name', str, "%(game)s"), ('name', str, "%(game)s"),
('servername', str, "%(server)s"), ('servername', str, "%(server)s"),
('teams', commaSeperatedStrings, ["Lobby", "Spectator", "Team one", "Team two", "Team three", "Team four"]), ('teams', commaSeperatedStrings, ["Lobby", "Spectator", "Team one", "Team two", "Team three", "Team four"]),
('restrict', x2bool, True), ('restrict', x2bool, True),
('serverregex', re.compile, re.compile("^\[[\w\d\-\(\):]{1,20}\]$")), ('serverregex', re.compile, re.compile("^\[[\w\d\-\(\):]{1,20}\]$")),
('deleteifunused', x2bool, True) ('deleteifunused', x2bool, True)
) )
default_config = {'source':( default_config = {'source': (
('database', str, "source.sqlite"), ('database', str, "source.sqlite"),
('basechannelid', int, 0), ('basechannelid', int, 0),
('mumbleservers', commaSeperatedIntegers, []), ('mumbleservers', commaSeperatedIntegers, []),
('gameregex', re.compile, re.compile("^(tf|dod|cstrike|hl2mp)$")), ('gameregex', re.compile, re.compile("^(tf|dod|cstrike|hl2mp)$")),
('groupprefix', str, "source_") ('groupprefix', str, "source_")
), ),
# The generic section defines default values which can be overridden in # The generic section defines default values which can be overridden in
# optional game specific "game:<gameshorthand>" sections # optional game specific "game:<gameshorthand>" sections
'generic': default_game_config,
lambda x: re.match('^game:\w+$', x): default_game_config
}
'generic': default_game_config,
lambda x: re.match('^game:\w+$', x): default_game_config
}
def __init__(self, name, manager, configuration=None): def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration) MumoModule.__init__(self, name, manager, configuration)
self.murmur = manager.getMurmurModule() self.murmur = manager.getMurmurModule()
@@ -84,11 +83,11 @@ class source(MumoModule):
MumoModule.onStart(self) MumoModule.onStart(self)
cfg = self.cfg() cfg = self.cfg()
self.db = SourceDB(cfg.source.database) self.db = SourceDB(cfg.source.database)
def onStop(self): def onStop(self):
MumoModule.onStop(self) MumoModule.onStop(self)
self.db.close() self.db.close()
def connected(self): def connected(self):
""" """
Makes sure the the plugin is correctly configured once the connection Makes sure the the plugin is correctly configured once the connection
@@ -98,21 +97,20 @@ class source(MumoModule):
manager = self.manager() manager = self.manager()
log = self.log() log = self.log()
log.debug("Register for Server callbacks") log.debug("Register for Server callbacks")
self.meta = manager.getMeta() self.meta = manager.getMeta()
servers = set(cfg.source.mumbleservers) servers = set(cfg.source.mumbleservers)
if not servers: if not servers:
servers = manager.SERVERS_ALL servers = manager.SERVERS_ALL
self.users = UserRegistry() self.users = UserRegistry()
self.validateChannelDB() self.validateChannelDB()
manager.subscribeServerCallbacks(self, servers) manager.subscribeServerCallbacks(self, servers)
manager.subscribeMetaCallbacks(self, servers) manager.subscribeMetaCallbacks(self, servers)
def validateChannelDB(self): def validateChannelDB(self):
""" """
Makes sure the plugins internal datatbase Makes sure the plugins internal datatbase
@@ -120,32 +118,33 @@ class source(MumoModule):
""" """
log = self.log() log = self.log()
log.debug("Validating channel database") log.debug("Validating channel database")
current_sid = -1 current_sid = -1
current_mumble_server = None current_mumble_server = None
for sid, cid, game, server, team in self.db.registeredChannels(): for sid, cid, game, server, team in self.db.registeredChannels():
if current_sid != sid: if current_sid != sid:
current_mumble_server = self.meta.getServer(sid) current_mumble_server = self.meta.getServer(sid)
current_sid = sid current_sid = sid
try: try:
state = current_mumble_server.getChannelState(cid) state = current_mumble_server.getChannelState(cid)
self.db.mapName(state.name, sid, game, server, team) self.db.mapName(state.name, sid, game, server, team)
#TODO: Verify ACL? # TODO: Verify ACL?
except self.murmur.InvalidChannelException: except self.murmur.InvalidChannelException:
# Channel no longer exists # Channel no longer exists
log.debug("(%d) Channel %d no longer exists. Dropped.", sid, cid) log.debug("(%d) Channel %d no longer exists. Dropped.", sid, cid)
self.db.dropChannel(sid, cid) self.db.dropChannel(sid, cid)
except AttributeError: except AttributeError:
# Server no longer exists # 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) log.debug("(%d) Server for channel %d no longer exists. Dropped.", sid, cid)
self.db.dropChannel(sid, cid) self.db.dropChannel(sid, cid)
def disconnected(self): pass def disconnected(self):
pass
def removeFromGroups(self, mumble_server, session, game, server, team): def removeFromGroups(self, mumble_server, session, game, server, team):
""" """
Removes the client from all relevant groups Removes the client from all relevant groups
@@ -153,16 +152,16 @@ class source(MumoModule):
sid = mumble_server.id() sid = mumble_server.id()
prefix = self.cfg().source.groupprefix prefix = self.cfg().source.groupprefix
game_cid = self.db.cidFor(sid, game) game_cid = self.db.cidFor(sid, game)
group = prefix + game group = prefix + game
mumble_server.removeUserFromGroup(game_cid, session, group) # Game mumble_server.removeUserFromGroup(game_cid, session, group) # Game
group += "_" + server group += "_" + server
mumble_server.removeUserFromGroup(game_cid, session, group) # Server mumble_server.removeUserFromGroup(game_cid, session, group) # Server
group += "_" + str(team) group += "_" + str(team)
mumble_server.removeUserFromGroup(game_cid, session, group) # Team mumble_server.removeUserFromGroup(game_cid, session, group) # Team
def addToGroups(self, mumble_server, session, game, server, team): def addToGroups(self, mumble_server, session, game, server, team):
""" """
Adds the client to all relevant groups Adds the client to all relevant groups
@@ -170,49 +169,48 @@ class source(MumoModule):
sid = mumble_server.id() sid = mumble_server.id()
prefix = self.cfg().source.groupprefix prefix = self.cfg().source.groupprefix
game_cid = self.db.cidFor(sid, game) game_cid = self.db.cidFor(sid, game)
assert(game_cid != None) assert (game_cid is not None)
group = prefix + game group = prefix + game
mumble_server.addUserToGroup(game_cid, session, group) # Game mumble_server.addUserToGroup(game_cid, session, group) # Game
group += "_" + server group += "_" + server
mumble_server.addUserToGroup(game_cid, session, group) # Server mumble_server.addUserToGroup(game_cid, session, group) # Server
group += "_" + str(team) group += "_" + str(team)
mumble_server.addUserToGroup(game_cid, session, group) # Team mumble_server.addUserToGroup(game_cid, session, group) # Team
def transitionPresentUser(self, mumble_server, old, new, sid, user_new): def transitionPresentUser(self, mumble_server, old, new, sid, user_new):
""" """
Transitions a user that has been and is currently playing Transitions a user that has been and is currently playing
""" """
assert(new) assert new
target_cid = self.getOrCreateTargetChannelFor(mumble_server, new) target_cid = self.getOrCreateTargetChannelFor(mumble_server, new)
if user_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"]) self.addToGroups(mumble_server, new.state.session, new.game, new.server, new.identity["team"])
else: else:
assert old assert old
self.dlog(sid, old.state, "User switched: g/s/t %s/%s/%d", new.game, new.server, new.identity["team"]) self.dlog(sid, old.state, "User switched: g/s/t %s/%s/%d", new.game, new.server, new.identity["team"])
self.removeFromGroups(mumble_server, old.state.session, old.game, old.server, old.identity["team"]) self.removeFromGroups(mumble_server, old.state.session, old.game, old.server, old.identity["team"])
self.addToGroups(mumble_server, new.state.session, new.game, new.server, new.identity["team"]) self.addToGroups(mumble_server, new.state.session, new.game, new.server, new.identity["team"])
return self.moveUser(mumble_server, new, target_cid)
return self.moveUser(mumble_server, new, target_cid)
def transitionGoneUser(self, mumble_server, old, new, sid): def transitionGoneUser(self, mumble_server, old, new, sid):
""" """
Transitions a user that played but is no longer doing so now. Transitions a user that played but is no longer doing so now.
""" """
assert(old) assert old
self.users.remove(sid, old.state.session) self.users.remove(sid, old.state.session)
if new: if new:
self.removeFromGroups(mumble_server, old.state.session, old.game, old.server, old.identity["team"]) self.removeFromGroups(mumble_server, old.state.session, old.game, old.server, old.identity["team"])
bcid = self.cfg().source.basechannelid bcid = self.cfg().source.basechannelid
self.dlog(sid, old.state, "User stopped playing. Moving to %d.", bcid) self.dlog(sid, old.state, "User stopped playing. Moving to %d.", bcid)
self.moveUserToCid(mumble_server, new.state, bcid) self.moveUserToCid(mumble_server, new.state, bcid)
@@ -220,7 +218,6 @@ class source(MumoModule):
self.dlog(sid, old.state, "User gone") self.dlog(sid, old.state, "User gone")
return True return True
def userLeftChannel(self, mumble_server, old, sid): def userLeftChannel(self, mumble_server, old, sid):
""" """
User left channel. Make sure we check for vacancy it if the game it User left channel. Make sure we check for vacancy it if the game it
@@ -247,37 +244,36 @@ class source(MumoModule):
""" """
sid = mumble_server.id() sid = mumble_server.id()
assert(not old or old.valid()) assert (not old or old.valid())
relevant = old or (new and new.valid()) relevant = old or (new and new.valid())
if not relevant: if not relevant:
return return
user_new = not old and new and new.valid() user_new = not old and new and new.valid()
user_gone = old and (not new or not new.valid()) user_gone = old and (not new or not new.valid())
if not user_gone: if not user_gone:
moved = self.transitionPresentUser(mumble_server, old, new, sid, user_new) moved = self.transitionPresentUser(mumble_server, old, new, sid, user_new)
else: else:
moved = self.transitionGoneUser(mumble_server, old, new, sid) moved = self.transitionGoneUser(mumble_server, old, new, sid)
if moved and old: if moved and old:
self.userLeftChannel(mumble_server, old, sid) self.userLeftChannel(mumble_server, old, sid)
def getGameName(self, game): def getGameName(self, game):
""" """
Returns the unexpanded game specific game name template. Returns the unexpanded game specific game name template.
""" """
return self.getGameConfig(game, "name") return self.getGameConfig(game, "name")
def getServerName(self, game): def getServerName(self, game):
""" """
Returns the unexpanded game specific server name template. Returns the unexpanded game specific server name template.
""" """
return self.getGameConfig(game, "servername") return self.getGameConfig(game, "servername")
def getTeamName(self, game, index): def getTeamName(self, game, index):
""" """
Returns the game specific team name for the given team index. Returns the game specific team name for the given team index.
@@ -287,32 +283,31 @@ class source(MumoModule):
return self.getGameConfig(game, "teams")[index] return self.getGameConfig(game, "teams")[index]
except IndexError: except IndexError:
return str(index) return str(index)
def setACLsForGameChannel(self, mumble_server, game_cid, game): def setACLsForGameChannel(self, mumble_server, game_cid, game):
""" """
Sets the appropriate ACLs for a game channel for the given cid. Sets the appropriate ACLs for a game channel for the given cid.
""" """
# Shorthands # Shorthands
ACL = self.murmur.ACL ACL = self.murmur.ACL
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
W = self.murmur.PermissionWhisper # Whisper W = self.murmur.PermissionWhisper # Whisper
S = self.murmur.PermissionSpeak # Speak S = self.murmur.PermissionSpeak # Speak
groupname = '~' + self.cfg().source.groupprefix + game groupname = '~' + self.cfg().source.groupprefix + game
mumble_server.setACL(game_cid, mumble_server.setACL(game_cid,
[ACL(applyHere = True, # Deny everything [ACL(applyHere=True, # Deny everything
applySubs = True, applySubs=True,
userid = -1, userid=-1,
group = 'all', group='all',
deny = EAT | W | S), deny=EAT | W | S),
ACL(applyHere = True, # Allow enter and traverse to players ACL(applyHere=True, # Allow enter and traverse to players
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = groupname, group=groupname,
allow = EAT)], allow=EAT)],
[], True) [], True)
def setACLsForServerChannel(self, mumble_server, server_cid, game, server): def setACLsForServerChannel(self, mumble_server, server_cid, game, server):
""" """
@@ -320,20 +315,19 @@ class source(MumoModule):
""" """
# Shorthands # Shorthands
ACL = self.murmur.ACL ACL = self.murmur.ACL
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
W = self.murmur.PermissionWhisper # Whisper W = self.murmur.PermissionWhisper # Whisper
S = self.murmur.PermissionSpeak # Speak S = self.murmur.PermissionSpeak # Speak
groupname = '~' + self.cfg().source.groupprefix + game + "_" + server groupname = '~' + self.cfg().source.groupprefix + game + "_" + server
mumble_server.setACL(server_cid, mumble_server.setACL(server_cid,
[ACL(applyHere = True, # Allow enter and traverse to players [ACL(applyHere=True, # Allow enter and traverse to players
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = groupname, group=groupname,
allow = EAT)], allow=EAT)],
[], True) [], True)
def setACLsForTeamChannel(self, mumble_server, team_cid, game, server, team): def setACLsForTeamChannel(self, mumble_server, team_cid, game, server, team):
""" """
@@ -341,19 +335,19 @@ class source(MumoModule):
""" """
# Shorthands # Shorthands
ACL = self.murmur.ACL ACL = self.murmur.ACL
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
W = self.murmur.PermissionWhisper # Whisper W = self.murmur.PermissionWhisper # Whisper
S = self.murmur.PermissionSpeak # Speak S = self.murmur.PermissionSpeak # Speak
groupname = '~' + self.cfg().source.groupprefix + game + "_" + server + "_" + str(team) groupname = '~' + self.cfg().source.groupprefix + game + "_" + server + "_" + str(team)
mumble_server.setACL(team_cid, mumble_server.setACL(team_cid,
[ACL(applyHere = True, # Allow enter and traverse to players [ACL(applyHere=True, # Allow enter and traverse to players
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = groupname, group=groupname,
allow = EAT | W | S)], allow=EAT | W | S)],
[], True) [], True)
def getOrCreateGameChannelFor(self, mumble_server, game, server, sid, cfg, log, namevars): def getOrCreateGameChannelFor(self, mumble_server, game, server, sid, cfg, log, namevars):
""" """
@@ -362,23 +356,22 @@ class source(MumoModule):
""" """
sid = mumble_server.id() sid = mumble_server.id()
game_cid = self.db.cidFor(sid, game) game_cid = self.db.cidFor(sid, game)
if game_cid == None: if game_cid is None:
game_channel_name = self.db.nameFor(sid, game, 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) 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) game_cid = mumble_server.addChannel(game_channel_name, cfg.source.basechannelid)
self.db.registerChannel(sid, game_cid, game) # Make sure we don't have orphaned server channels around self.db.registerChannel(sid, game_cid, game) # Make sure we don't have orphaned server channels around
self.db.unregisterChannel(sid, game, server) self.db.unregisterChannel(sid, game, server)
if self.getGameConfig(game, "restrict"): if self.getGameConfig(game, "restrict"):
log.debug("(%d) Setting ACL's for new game channel (cid %d)", sid, game_cid) log.debug("(%d) Setting ACL's for new game channel (cid %d)", sid, game_cid)
self.setACLsForGameChannel(mumble_server, game_cid, game) self.setACLsForGameChannel(mumble_server, game_cid, game)
log.debug("(%d) Game channel created and registered (cid %d)", sid, game_cid) log.debug("(%d) Game channel created and registered (cid %d)", sid, game_cid)
return game_cid return game_cid
def getOrCreateServerChannelFor(self, mumble_server, game, server, team, sid, log, namevars, 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 Helper function for getting or creating only the server channel. The game
@@ -386,43 +379,42 @@ class source(MumoModule):
server channel. server channel.
""" """
server_cid = self.db.cidFor(sid, game, server) 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, 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) 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) server_cid = mumble_server.addChannel(server_channel_name, game_cid)
self.db.registerChannel(sid, server_cid, game, server) self.db.registerChannel(sid, server_cid, game, server)
self.db.unregisterChannel(sid, game, server, team) # Make sure we don't have orphaned team channels around self.db.unregisterChannel(sid, game, server, team) # Make sure we don't have orphaned team channels around
if self.getGameConfig(game, "restrict"): if self.getGameConfig(game, "restrict"):
log.debug("(%d) Setting ACL's for new server channel (cid %d)", sid, server_cid) log.debug("(%d) Setting ACL's for new server channel (cid %d)", sid, server_cid)
self.setACLsForServerChannel(mumble_server, server_cid, game, server) self.setACLsForServerChannel(mumble_server, server_cid, game, server)
log.debug("(%d) Server channel created and registered (cid %d)", sid, server_cid) log.debug("(%d) Server channel created and registered (cid %d)", sid, server_cid)
return server_cid return server_cid
def getOrCreateTeamChannelFor(self, mumble_server, game, server, team, sid, log, 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 Helper function for getting or creating only the team channel. Game and
server channel must already exist. Returns the cid of the existing or server channel must already exist. Returns the cid of the existing or
created team channel. created team channel.
""" """
team_cid = self.db.cidFor(sid, game, server, team) 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, 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) 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) team_cid = mumble_server.addChannel(team_channel_name, server_cid)
self.db.registerChannel(sid, team_cid, game, server, team) self.db.registerChannel(sid, team_cid, game, server, team)
if self.getGameConfig(game, "restrict"): if self.getGameConfig(game, "restrict"):
log.debug("(%d) Setting ACL's for new team channel (cid %d)", sid, team_cid) log.debug("(%d) Setting ACL's for new team channel (cid %d)", sid, team_cid)
self.setACLsForTeamChannel(mumble_server, team_cid, game, server, team) self.setACLsForTeamChannel(mumble_server, team_cid, game, server, team)
log.debug("(%d) Team channel created and registered (cid %d)", sid, team_cid) log.debug("(%d) Team channel created and registered (cid %d)", sid, team_cid)
return team_cid return team_cid
@@ -435,16 +427,16 @@ class source(MumoModule):
sid = mumble_server.id() sid = mumble_server.id()
cfg = self.cfg() cfg = self.cfg()
log = self.log() log = self.log()
namevars = {'game' : game, namevars = {'game': game,
'server' : server} 'server': server}
game_cid = self.getOrCreateGameChannelFor(mumble_server, game, server, sid, cfg, log, namevars) 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) server_cid = self.getOrCreateServerChannelFor(mumble_server, game, server, team, sid, log, namevars, game_cid)
team_cid = self.getOrCreateTeamChannelFor(mumble_server, game, server, team, sid, log, server_cid) team_cid = self.getOrCreateTeamChannelFor(mumble_server, game, server, team, sid, log, server_cid)
return team_cid return team_cid
def moveUserToCid(self, server, state, cid): def moveUserToCid(self, server, state, cid):
""" """
Low level helper for moving a user to a channel known by its ID Low level helper for moving a user to a channel known by its ID
@@ -452,7 +444,7 @@ class source(MumoModule):
self.dlog(server.id(), state, "Moving from channel %d to %d", state.channel, cid) self.dlog(server.id(), state, "Moving from channel %d to %d", state.channel, cid)
state.channel = cid state.channel = cid
server.setState(state) server.setState(state)
def getOrCreateTargetChannelFor(self, mumble_server, user): def getOrCreateTargetChannelFor(self, mumble_server, user):
""" """
Returns the cid of the target channel for this user. If needed Returns the cid of the target channel for this user. If needed
@@ -462,8 +454,8 @@ class source(MumoModule):
user.game, user.game,
user.server, user.server,
user.identity["team"]) 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. Move user according to current game state.
@@ -477,21 +469,21 @@ class source(MumoModule):
server = user.server server = user.server
team = user.identity["team"] team = user.identity["team"]
sid = mumble_server.id() sid = mumble_server.id()
source_cid = state.channel source_cid = state.channel
if target_cid == None: if target_cid is None:
target_cid = self.getOrCreateChannelFor(mumble_server, game, server, team) target_cid = self.getOrCreateChannelFor(mumble_server, game, server, team)
if source_cid != target_cid: if source_cid != target_cid:
self.moveUserToCid(mumble_server, state, target_cid) self.moveUserToCid(mumble_server, state, target_cid)
user.state.channel = target_cid user.state.channel = target_cid
self.users.addOrUpdate(sid, state.session, user) self.users.addOrUpdate(sid, state.session, user)
return True return True
return False return False
def deleteIfUnused(self, mumble_server, cid): def deleteIfUnused(self, mumble_server, cid):
""" """
Takes the cid of a server or team channel and checks if all Takes the cid of a server or team channel and checks if all
@@ -500,46 +492,46 @@ class source(MumoModule):
Note: Assumes tree structure Note: Assumes tree structure
""" """
sid = mumble_server.id() sid = mumble_server.id()
log = self.log() log = self.log()
result = self.db.channelForCid(sid, cid) result = self.db.channelForCid(sid, cid)
if not result: if not result:
return False return False
_, _, cur_game, cur_server, cur_team = result _, _, cur_game, cur_server, cur_team = result
assert(cur_game) assert cur_game
if not cur_server: if not cur_server:
# Don't handle game channels # Don't handle game channels
log.debug("(%d) Delete if unused on game channel %d, ignoring", sid, cid) log.debug("(%d) Delete if unused on game channel %d, ignoring", sid, cid)
return False return False
server_channel_cid = None server_channel_cid = None
relevant = self.db.channelsFor(sid, cur_game, cur_server) relevant = self.db.channelsFor(sid, cur_game, cur_server)
for _, cur_cid, _, _, cur_team in relevant: for _, cur_cid, _, _, cur_team in relevant:
if cur_team == self.db.NO_TEAM: if cur_team == self.db.NO_TEAM:
server_channel_cid = cur_cid server_channel_cid = cur_cid
if self.users.usingChannel(sid, cur_cid): if self.users.usingChannel(sid, cur_cid):
log.debug("(%d) Delete if unused: Channel %d in use", sid, cur_cid) log.debug("(%d) Delete if unused: Channel %d in use", sid, cur_cid)
return False # Used return False # Used
assert(server_channel_cid != None) assert (server_channel_cid is not None)
# Unused. Delete server and children # Unused. Delete server and children
log.debug("(%s) Channel %d unused. Will be deleted.", sid, server_channel_cid) log.debug("(%s) Channel %d unused. Will be deleted.", sid, server_channel_cid)
mumble_server.removeChannel(server_channel_cid) mumble_server.removeChannel(server_channel_cid)
return True return True
def isValidGameType(self, game): 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): 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): def parseSourceContext(self, context):
""" """
Parse source engine context string. Returns tuple with Parse source engine context string. Returns tuple with
@@ -549,19 +541,19 @@ class source(MumoModule):
try: try:
prefix, server = context.split('\x00')[0:2] prefix, server = context.split('\x00')[0:2]
source, game = [s.strip() for s in prefix.split(':', 1)] source, game = [s.strip() for s in prefix.split(':', 1)]
if source != "Source engine": if source != "Source engine":
# Not a source engine context # Not a source engine context
return (None, None) return None, None
if not self.isValidGameType(game) or not self.isValidServer(game, server): 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: except (AttributeError, ValueError) as e:
return (None, None); return None, None
def parseSourceIdentity(self, identity): def parseSourceIdentity(self, identity):
""" """
Parse comma separated source engine identity string key value pairs Parse comma separated source engine identity string key value pairs
@@ -574,31 +566,32 @@ class source(MumoModule):
d = {} d = {}
for k, v in [var.split(':', 1) for var in identity.split(';')]: for k, v in [var.split(':', 1) for var in identity.split(';')]:
d[k] = int(v) d[k] = int(v)
# Make sure mandatory values are present # Make sure mandatory values are present
if not "team" in d: return None if "team" not in d:
return None
return d
return d
except (AttributeError, ValueError): except (AttributeError, ValueError):
return None return None
def getGameConfig(self, game, variable): def getGameConfig(self, game, variable):
""" """
Return the game specific value for the given variable if it exists. Otherwise the generic value Return the game specific value for the given variable if it exists. Otherwise the generic value
""" """
sectionname = "game:" + game sectionname = "game:" + game
cfg = self.cfg() cfg = self.cfg()
if sectionname not in cfg: if sectionname not in cfg:
return cfg.generic[variable] return cfg.generic[variable]
return cfg[sectionname][variable] return cfg[sectionname][variable]
def dlog(self, sid, state, what, *argc): def dlog(self, sid, state, what, *argc):
""" Debug log output helper for user state related things """ """ Debug log output helper for user state related things """
self.log().debug("(%d) (%d|%d) " + what, sid, state.session, state.userid, *argc) self.log().debug("(%d) (%d|%d) " + what, sid, state.session, state.userid, *argc)
def handle(self, server, new_state): def handle(self, server, new_state):
""" """
Takes the updated state of the user and collects all Takes the updated state of the user and collects all
@@ -607,32 +600,32 @@ class source(MumoModule):
""" """
sid = server.id() sid = server.id()
session = new_state.session session = new_state.session
self.dlog(sid, new_state, "Handle state change") self.dlog(sid, new_state, "Handle state change")
old_user = self.users.get(sid, session) old_user = self.users.get(sid, session)
if old_user and not old_user.hasContextOrIdentityChanged(new_state): if old_user and not old_user.hasContextOrIdentityChanged(new_state):
# No change in relevant fields. Simply update state for reference # No change in relevant fields. Simply update state for reference
old_user.updateState(new_state) old_user.updateState(new_state)
self.dlog(sid, new_state, "State change irrelevant for plugin") self.dlog(sid, new_state, "State change irrelevant for plugin")
return return
game, game_server = self.parseSourceContext(new_state.context) game, game_server = self.parseSourceContext(new_state.context)
identity = self.parseSourceIdentity(new_state.identity) identity = self.parseSourceIdentity(new_state.identity)
self.dlog(sid, new_state, "Context: %s -> '%s' / '%s'", repr(new_state.context), game, game_server) self.dlog(sid, new_state, "Context: %s -> '%s' / '%s'", repr(new_state.context), game, game_server)
self.dlog(sid, new_state, "Identity: %s -> '%s'", repr(new_state.identity), identity) self.dlog(sid, new_state, "Identity: %s -> '%s'", repr(new_state.identity), identity)
updated_user = User(new_state, identity, game, game_server) updated_user = User(new_state, identity, game, game_server)
self.dlog(sid, new_state, "Starting transition") self.dlog(sid, new_state, "Starting transition")
self.userTransition(server, old_user, updated_user) self.userTransition(server, old_user, updated_user)
self.dlog(sid, new_state, "Transition complete") self.dlog(sid, new_state, "Transition complete")
# #
#--- Server callback functions # --- Server callback functions
# #
def userDisconnected(self, server, state, context=None): def userDisconnected(self, server, state, context=None):
""" """
Handle disconnect to be able to delete unused channels Handle disconnect to be able to delete unused channels
@@ -640,9 +633,9 @@ class source(MumoModule):
""" """
sid = server.id() sid = server.id()
session = state.session session = state.session
self.userTransition(server, self.users.get(sid, session), None) self.userTransition(server, self.users.get(sid, session), None)
def userStateChanged(self, server, state, context=None): def userStateChanged(self, server, state, context=None):
""" """
Default state change for user. Could be something uninteresting for Default state change for user. Could be something uninteresting for
@@ -650,21 +643,21 @@ class source(MumoModule):
string change triggered by starting to play. string change triggered by starting to play.
""" """
self.handle(server, state) self.handle(server, state)
def userConnected(self, server, state, context=None): def userConnected(self, server, state, context=None):
""" """
First time we see the state for a user. userStateChanged behavior First time we see the state for a user. userStateChanged behavior
applies. applies.
""" """
self.handle(server, state) self.handle(server, state)
def channelRemoved(self, server, state, context=None): def channelRemoved(self, server, state, context=None):
""" """
Updates internal accounting for channels controlled by the plugin. Updates internal accounting for channels controlled by the plugin.
""" """
cid = state.id cid = state.id
sid = server.id() sid = server.id()
self.log().debug("(%d) Channel %d removed.", sid, cid) self.log().debug("(%d) Channel %d removed.", sid, cid)
self.db.dropChannel(sid, cid) self.db.dropChannel(sid, cid)
@@ -680,17 +673,19 @@ class source(MumoModule):
_, _, game, server, team = channel _, _, game, server, team = channel
self.db.mapName(name, sid, game, server, team) self.db.mapName(name, sid, game, server, team)
self.log().debug("(%d) Name mapping for channel %d updated to '%s'", sid, cid, name) 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): def started(self, server, context=None):
self.log().debug("Started") self.log().debug("Started")
def stopped(self, server, context=None): def stopped(self, server, context=None):
self.log().debug("Stopped") self.log().debug("Stopped")

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net> # 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 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest import queue
import Queue
import config
import re import re
import logging import unittest
import source
import config
from . import source
class InvalidChannelExceptionMock(Exception): class InvalidChannelExceptionMock(Exception):
pass pass
class StateMock(): class StateMock():
def __init__(self, cid = 0, session = 0, userid = -1): def __init__(self, cid=0, session=0, userid=-1):
self.channel = cid self.channel = cid
self.session = session self.session = session
self.userid = userid self.userid = userid
class ChannelStateMock(): class ChannelStateMock():
def __init__(self, cid, name, parent, groups, acls): def __init__(self, cid, name, parent, groups, acls):
self.id = cid self.id = cid
@@ -52,66 +55,68 @@ class ChannelStateMock():
self.parent = parent self.parent = parent
self.groups = groups self.groups = groups
self.acls = acls self.acls = acls
class ServerMock(): class ServerMock():
def __init__(self, sid): def __init__(self, sid):
self.sid = sid self.sid = sid
self._reset() self._reset()
def id(self): def id(self):
return self.sid return self.sid
def _lastChannelID(self): def _lastChannelID(self):
return self.uid return self.uid
def addChannel(self, name, parent): def addChannel(self, name, parent):
self.uid += 1 self.uid += 1
assert(not self.uid in self.channels) assert (not self.uid in self.channels)
self.channels[self.uid] = ChannelStateMock(self.uid, self.channels[self.uid] = ChannelStateMock(self.uid,
name, name,
parent, parent,
{}, {},
[]) [])
return self.uid return self.uid
def addUserToGroup(self, cid, session, group): def addUserToGroup(self, cid, session, group):
c = self._getChan(cid) c = self._getChan(cid)
if session in c.groups: if session in c.groups:
c.groups[session].add(group) c.groups[session].add(group)
else: else:
c.groups[session] = set([group]) c.groups[session] = {group}
def _getChan(self, cid): def _getChan(self, cid):
if not cid in self.channels: if cid not in self.channels:
raise InvalidChannelExceptionMock() raise InvalidChannelExceptionMock()
return self.channels[cid] return self.channels[cid]
def getChannelState(self, cid): def getChannelState(self, cid):
return self._getChan(cid) return self._getChan(cid)
def setState(self, state): def setState(self, state):
self.user_state.append(state) self.user_state.append(state)
def setACL(self, cid, acls, groups, inherit): def setACL(self, cid, acls, groups, inherit):
c = self._getChan(cid) c = self._getChan(cid)
c.acls = acls c.acls = acls
def _reset(self): def _reset(self):
self.uid = 1000 self.uid = 1000
self.channels = {} # See addChannel self.channels = {} # See addChannel
self.user_state = [] self.user_state = []
class ACLMock(object): 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.applyHere = applyHere
self.applySubs = applySubs self.applySubs = applySubs
self.userid = userid self.userid = userid
self.group = group self.group = group
self.deny = deny self.deny = deny
self.allow = allow self.allow = allow
class MurmurMock(object): class MurmurMock(object):
InvalidChannelException = InvalidChannelExceptionMock InvalidChannelException = InvalidChannelExceptionMock
@@ -120,9 +125,9 @@ class MurmurMock(object):
PermissionTraverse = 2 PermissionTraverse = 2
PermissionWhisper = 4 PermissionWhisper = 4
PermissionSpeak = 8 PermissionSpeak = 8
def _reset(self): pass def _reset(self): pass
def __init__(self): def __init__(self):
pass pass
@@ -132,61 +137,63 @@ class MockACLHelper(object):
T = MurmurMock.PermissionTraverse T = MurmurMock.PermissionTraverse
W = MurmurMock.PermissionWhisper W = MurmurMock.PermissionWhisper
S = MurmurMock.PermissionSpeak S = MurmurMock.PermissionSpeak
EAT = E | T EAT = E | T
ALL = E|T|W|S ALL = E | T | W | S
ACLS = MockACLHelper ACLS = MockACLHelper
class MetaMock(): class MetaMock():
def __init__(self): def __init__(self):
#TODO: Create range of server (or even cretae them on demand) # TODO: Create range of server (or even cretae them on demand)
self.servers = {1:ServerMock(1), self.servers = {1: ServerMock(1),
5:ServerMock(5), 5: ServerMock(5),
10:ServerMock(10)} 10: ServerMock(10)}
self.s = self.servers[1] # Shorthand self.s = self.servers[1] # Shorthand
def getServer(self, sid): def getServer(self, sid):
return self.servers.get(sid, None) return self.servers.get(sid, None)
def _reset(self): def _reset(self):
for server in self.servers.itervalues(): for server in self.servers.values():
server._reset() server._reset()
class ManagerMock(): class ManagerMock():
SERVERS_ALL = [-1] SERVERS_ALL = [-1]
def __init__(self): def __init__(self):
self.q = Queue.Queue() self.q = queue.Queue()
self.m = MurmurMock() self.m = MurmurMock()
self.meta = MetaMock() self.meta = MetaMock()
def getQueue(self): def getQueue(self):
return self.q return self.q
def getMurmurModule(self): def getMurmurModule(self):
return self.m return self.m
def getMeta(self): def getMeta(self):
return self.meta return self.meta
def subscribeServerCallbacks(self, callback, servers): def subscribeServerCallbacks(self, callback, servers):
self.serverCB = {'callback' : callback, 'servers' : servers} self.serverCB = {'callback': callback, 'servers': servers}
def subscribeMetaCallbacks(self, callback, servers): def subscribeMetaCallbacks(self, callback, servers):
self.metaCB = {'callback' : callback, 'servers' : servers} self.metaCB = {'callback': callback, 'servers': servers}
class Test(unittest.TestCase): class Test(unittest.TestCase):
def setUp(self): def setUp(self):
self.mm = ManagerMock(); self.mm = ManagerMock();
self.mserv = self.mm.meta.getServer(1) self.mserv = self.mm.meta.getServer(1)
testconfig = config.Config(None, source.source.default_config) testconfig = config.Config(None, source.source.default_config)
testconfig.source.database = ":memory:" testconfig.source.database = ":memory:"
# As it is hard to create the read only config structure from # As it is hard to create the read only config structure from
# hand use a spare one to steal from # hand use a spare one to steal from
spare = config.Config(None, source.source.default_config) spare = config.Config(None, source.source.default_config)
@@ -195,46 +202,46 @@ class Test(unittest.TestCase):
testconfig.__dict__['game:tf'].teams = ["Lobby", "Spectator", "Blue", "Red"] testconfig.__dict__['game:tf'].teams = ["Lobby", "Spectator", "Blue", "Red"]
testconfig.__dict__['game:tf'].serverregex = re.compile("^\[A-1:123\]$") testconfig.__dict__['game:tf'].serverregex = re.compile("^\[A-1:123\]$")
testconfig.__dict__['game:tf'].servername = "Test %(game)s %(server)s" testconfig.__dict__['game:tf'].servername = "Test %(game)s %(server)s"
self.s = source.source("source", self.mm, testconfig) self.s = source.source("source", self.mm, testconfig)
self.mm.s = self.s self.mm.s = self.s
# Since we don't want to run threaded if we don't have to # Since we don't want to run threaded if we don't have to
# emulate startup to the derived class function # emulate startup to the derived class function
self.s.onStart() self.s.onStart()
self.s.connected() self.s.connected()
# Critical test assumption # Critical test assumption
self.assertEqual(self.mm.metaCB['callback'], self.s) self.assertEqual(self.mm.metaCB['callback'], self.s)
self.assertEqual(self.mm.serverCB['callback'], self.s) self.assertEqual(self.mm.serverCB['callback'], self.s)
def resetDB(self): def resetDB(self):
self.s.db.reset() self.s.db.reset()
def resetState(self): def resetState(self):
self.resetDB() self.resetDB()
self.mm.m._reset() self.mm.m._reset()
self.mm.meta._reset() self.mm.meta._reset()
def tearDown(self): def tearDown(self):
self.s.disconnected() self.s.disconnected()
self.s.onStop() self.s.onStop()
def testDefaultConfig(self): def testDefaultConfig(self):
self.resetState() self.resetState()
mm = ManagerMock() mm = ManagerMock()
INVALIDFORCEDEFAULT = "" INVALIDFORCEDEFAULT = ""
s = source.source("source", mm, INVALIDFORCEDEFAULT) s = source.source("source", mm, INVALIDFORCEDEFAULT)
self.assertNotEqual(s.cfg(), None) self.assertNotEqual(s.cfg(), None)
def testConfiguration(self): def testConfiguration(self):
self.resetState() self.resetState()
# Ensure the default configuration makes sense # Ensure the default configuration makes sense
self.assertEqual(self.mm.serverCB['servers'], self.mm.SERVERS_ALL) self.assertEqual(self.mm.serverCB['servers'], self.mm.SERVERS_ALL)
self.assertEqual(self.mm.metaCB['servers'], self.mm.SERVERS_ALL) self.assertEqual(self.mm.metaCB['servers'], self.mm.SERVERS_ALL)
self.assertEqual(self.s.cfg().source.basechannelid, 0) self.assertEqual(self.s.cfg().source.basechannelid, 0)
self.assertEqual(self.s.cfg().generic.name, "%(game)s") self.assertEqual(self.s.cfg().generic.name, "%(game)s")
@@ -243,35 +250,35 @@ class Test(unittest.TestCase):
def testIdentityParser(self): def testIdentityParser(self):
self.resetState() self.resetState()
expected = {"universe" : 1, expected = {"universe": 1,
"account_type" : 2, "account_type": 2,
"id" : 3, "id": 3,
"instance" : 4, "instance": 4,
"team" : 5} "team": 5}
got = self.s.parseSourceIdentity("universe:1;account_type:2;id:00000003;instance:4;team:5") got = self.s.parseSourceIdentity("universe:1;account_type:2;id:00000003;instance:4;team:5")
self.assertDictEqual(expected, got) self.assertDictEqual(expected, got)
got = self.s.parseSourceIdentity("universe:1;account_type:2;id:00000003;instance:4;") got = self.s.parseSourceIdentity("universe:1;account_type:2;id:00000003;instance:4;")
self.assertEqual(got, None, "Required team variable missing") self.assertEqual(got, None, "Required team variable missing")
self.assertEqual(self.s.parseSourceIdentity(None), None) self.assertEqual(self.s.parseSourceIdentity(None), None)
self.assertEqual(self.s.parseSourceIdentity(""), None) self.assertEqual(self.s.parseSourceIdentity(""), None)
self.assertEqual(self.s.parseSourceIdentity("whatever:4;dskjfskjdfkjsfkjsfkj"), None) self.assertEqual(self.s.parseSourceIdentity("whatever:4;dskjfskjdfkjsfkjsfkj"), None)
def testContextParser(self): def testContextParser(self):
self.resetState() self.resetState()
none = (None, None) none = (None, None)
self.assertEqual(self.s.parseSourceContext(None), none) self.assertEqual(self.s.parseSourceContext(None), none)
self.assertEqual(self.s.parseSourceContext(""), none) self.assertEqual(self.s.parseSourceContext(""), none)
self.assertEqual(self.s.parseSourceContext("whatever:4;skjdakjkjwqdkjqkj"), none) self.assertEqual(self.s.parseSourceContext("whatever:4;skjdakjkjwqdkjqkj"), none)
expected = ("dod", "[A-1:2807761920(3281)]") expected = ("dod", "[A-1:2807761920(3281)]")
actual = self.s.parseSourceContext("Source engine: dod\x00[A-1:2807761920(3281)]\x00") actual = self.s.parseSourceContext("Source engine: dod\x00[A-1:2807761920(3281)]\x00")
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
expected = ("dod", "[0:1]") expected = ("dod", "[0:1]")
actual = self.s.parseSourceContext("Source engine: dod\x00[0:1]\x00") actual = self.s.parseSourceContext("Source engine: dod\x00[0:1]\x00")
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
@@ -279,266 +286,279 @@ class Test(unittest.TestCase):
expected = ("cstrike", "[0:1]") expected = ("cstrike", "[0:1]")
actual = self.s.parseSourceContext("Source engine: cstrike\x00[0:1]\x00") actual = self.s.parseSourceContext("Source engine: cstrike\x00[0:1]\x00")
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
actual = self.s.parseSourceContext("Source engine: fake\x00[A-1:2807761920(3281)]\x00") actual = self.s.parseSourceContext("Source engine: fake\x00[A-1:2807761920(3281)]\x00")
self.assertEqual(none, actual) self.assertEqual(none, actual)
actual = self.s.parseSourceContext("Source engine: cstrike\x0098vcv98re98ver98ver98v\x00") actual = self.s.parseSourceContext("Source engine: cstrike\x0098vcv98re98ver98ver98v\x00")
self.assertEqual(none, actual) self.assertEqual(none, actual)
# Check alternate serverregex # Check alternate serverregex
expected = ("tf", "[A-1:123]") expected = ("tf", "[A-1:123]")
actual = self.s.parseSourceContext("Source engine: tf\x00[A-1:123]\x00") actual = self.s.parseSourceContext("Source engine: tf\x00[A-1:123]\x00")
self.assertEqual(expected, actual) self.assertEqual(expected, actual)
actual = self.s.parseSourceContext("Source engine: tf\x00[A-1:2807761920(3281)]\x00") actual = self.s.parseSourceContext("Source engine: tf\x00[A-1:2807761920(3281)]\x00")
self.assertEqual(none, actual) self.assertEqual(none, actual)
def checkACLThings(self, acls, things): def checkACLThings(self, acls, things):
self.assertEqual(len(things), len(acls)) self.assertEqual(len(things), len(acls))
i = 0 i = 0
for thing in things: for thing in things:
acl = acls[i] acl = acls[i]
for attr, val in thing.iteritems(): for attr, val in thing.items():
self.assertEqual(getattr(acl, attr), val) self.assertEqual(getattr(acl, attr), val)
i += 1 i += 1
def testGetOrCreateChannelFor(self): def testGetOrCreateChannelFor(self):
mumble_server = self.mserv mumble_server = self.mserv
prev = mumble_server._lastChannelID() 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) cid = self.s.getOrCreateChannelFor(mumble_server, game, server, team)
self.assertEqual(3, cid - prev) self.assertEqual(3, cid - prev)
c = mumble_server.channels c = mumble_server.channels
self.assertEqual(c[prev + 1].parent, 0) self.assertEqual(c[prev + 1].parent, 0)
self.assertEqual(c[prev + 2].parent, prev + 1) self.assertEqual(c[prev + 2].parent, prev + 1)
self.assertEqual(c[prev + 3].parent, prev + 2) self.assertEqual(c[prev + 3].parent, prev + 2)
self.assertEqual(c[prev + 1].name, "Team Fortress 2") self.assertEqual(c[prev + 1].name, "Team Fortress 2")
self.assertEqual(c[prev + 2].name, "Test tf [A-1:123]") self.assertEqual(c[prev + 2].name, "Test tf [A-1:123]")
self.assertEqual(c[prev + 3].name, "Red") self.assertEqual(c[prev + 3].name, "Red")
sid = mumble_server.id() sid = mumble_server.id()
self.assertEqual(self.s.db.cidFor(sid, game), prev + 1); 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), prev + 2)
self.assertEqual(self.s.db.cidFor(sid, game, server, team), prev + 3); self.assertEqual(self.s.db.cidFor(sid, game, server, team), prev + 3)
gotcid = self.s.getOrCreateChannelFor(mumble_server, game, server, team) gotcid = self.s.getOrCreateChannelFor(mumble_server, game, server, team)
self.assertEqual(cid, gotcid) self.assertEqual(cid, gotcid)
c = mumble_server.channels c = mumble_server.channels
self.checkACLThings(c[prev + 3].acls, [{'group' : '~source_tf_[A-1:123]_3'}]) 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 + 2].acls, [{'group': '~source_tf_[A-1:123]'}])
self.checkACLThings(c[prev + 1].acls, [{}, 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): def testGetGameName(self):
self.resetState() self.resetState()
self.assertEqual(self.s.getGameName("tf"), "Team Fortress 2") 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): def testGetServerName(self):
self.resetState() self.resetState()
self.assertEqual(self.s.getServerName("tf"), "Test %(game)s %(server)s") 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): def testGetTeamName(self):
self.resetState() self.resetState()
self.assertEqual(self.s.getTeamName("tf", 2), "Blue") 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", 2), "Team one")
self.assertEqual(self.s.getTeamName("invalid", 100), "100") #oob self.assertEqual(self.s.getTeamName("invalid", 100), "100") # oob
def testValidGameType(self): def testValidGameType(self):
self.resetState() self.resetState()
self.assertTrue(self.s.isValidGameType("dod")) self.assertTrue(self.s.isValidGameType("dod"))
self.assertTrue(self.s.isValidGameType("cstrike")) self.assertTrue(self.s.isValidGameType("cstrike"))
self.assertTrue(self.s.isValidGameType("tf")) self.assertTrue(self.s.isValidGameType("tf"))
self.assertFalse(self.s.isValidGameType("dodx")) self.assertFalse(self.s.isValidGameType("dodx"))
self.assertFalse(self.s.isValidGameType("xdod")) self.assertFalse(self.s.isValidGameType("xdod"))
self.assertFalse(self.s.isValidGameType("")) self.assertFalse(self.s.isValidGameType(""))
def testValidServer(self): def testValidServer(self):
self.resetState() self.resetState()
self.assertTrue(self.s.isValidServer("dod", "[A-1:2807761920(3281)]")) self.assertTrue(self.s.isValidServer("dod", "[A-1:2807761920(3281)]"))
self.assertFalse(self.s.isValidServer("dod", "A-1:2807761920(3281)]")) self.assertFalse(self.s.isValidServer("dod", "A-1:2807761920(3281)]"))
self.assertFalse(self.s.isValidServer("dod", "[A-1:2807761920(3281)")) self.assertFalse(self.s.isValidServer("dod", "[A-1:2807761920(3281)"))
self.assertFalse(self.s.isValidServer("dod", "[A-1:2807761920(3281)&]")) self.assertFalse(self.s.isValidServer("dod", "[A-1:2807761920(3281)&]"))
self.assertTrue(self.s.isValidServer("tf", "[A-1:123]")) self.assertTrue(self.s.isValidServer("tf", "[A-1:123]"))
self.assertFalse(self.s.isValidServer("tf", "x[A-1:123]")) self.assertFalse(self.s.isValidServer("tf", "x[A-1:123]"))
self.assertFalse(self.s.isValidServer("tf", "[A-1:123]x")) self.assertFalse(self.s.isValidServer("tf", "[A-1:123]x"))
def testMoveUser(self): def testMoveUser(self):
self.resetState() self.resetState()
mumble_server = self.mserv mumble_server = self.mserv
user_state = StateMock() user_state = StateMock()
prev = self.mserv._lastChannelID() prev = self.mserv._lastChannelID()
TEAM_BLUE = 2 TEAM_BLUE = 2
TEAM_RED = 3 TEAM_RED = 3
BASE_SID = 0 BASE_SID = 0
GAME_SID = prev + 1 GAME_SID = prev + 1
SERVER_SID = prev + 2 SERVER_SID = prev + 2
TEAM_RED_SID = prev + 3 TEAM_RED_SID = prev + 3
TEAM_BLUE_SID = prev + 4 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) self.s.moveUser(self.mserv, user)
c = mumble_server.channels c = mumble_server.channels
self.assertEqual(c[prev + 1].parent, BASE_SID) self.assertEqual(c[prev + 1].parent, BASE_SID)
self.assertEqual(c[prev + 2].parent, GAME_SID) self.assertEqual(c[prev + 2].parent, GAME_SID)
self.assertEqual(c[prev + 3].parent, SERVER_SID) self.assertEqual(c[prev + 3].parent, SERVER_SID)
self.assertEqual(c[prev + 1].name, "Team Fortress 2") self.assertEqual(c[prev + 1].name, "Team Fortress 2")
self.assertEqual(c[prev + 2].name, "Test tf [A-1:123]") self.assertEqual(c[prev + 2].name, "Test tf [A-1:123]")
self.assertEqual(c[prev + 3].name, "Blue") self.assertEqual(c[prev + 3].name, "Blue")
self.assertEqual(len(c), 3) self.assertEqual(len(c), 3)
self.assertEqual(user_state.channel, TEAM_RED_SID) self.assertEqual(user_state.channel, TEAM_RED_SID)
self.assertEqual(mumble_server.user_state[0], user_state) self.assertEqual(mumble_server.user_state[0], user_state)
user.identity['team'] = TEAM_RED user.identity['team'] = TEAM_RED
self.s.moveUser(self.mserv, user) self.s.moveUser(self.mserv, user)
self.assertEqual(c[prev + 4].parent, SERVER_SID) self.assertEqual(c[prev + 4].parent, SERVER_SID)
self.assertEqual(c[prev + 4].name, "Red") self.assertEqual(c[prev + 4].name, "Red")
self.assertEqual(len(c), 4) self.assertEqual(len(c), 4)
self.assertEqual(user_state.channel, TEAM_BLUE_SID) self.assertEqual(user_state.channel, TEAM_BLUE_SID)
self.assertEqual(mumble_server.user_state[0], user_state) self.assertEqual(mumble_server.user_state[0], user_state)
def testValidateChannelDB(self): def testValidateChannelDB(self):
self.resetState() self.resetState()
self.s.db.registerChannel(5, 6, "7") self.s.db.registerChannel(5, 6, "7")
self.s.db.registerChannel(5, 7, "7", "8") self.s.db.registerChannel(5, 7, "7", "8")
self.s.db.registerChannel(5, 8, "7", "8", 9) self.s.db.registerChannel(5, 8, "7", "8", 9)
self.s.db.registerChannel(6, 9, "8", "9", 10) self.s.db.registerChannel(6, 9, "8", "9", 10)
self.s.db.registerChannel(5, 10, "7", "123", 9) 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.getOrCreateChannelFor(self.mserv, game, server, team)
self.s.validateChannelDB() self.s.validateChannelDB()
self.assertEqual(len(self.s.db.registeredChannels()), 3) self.assertEqual(len(self.s.db.registeredChannels()), 3)
def testSetACLsForGameChannel(self): def testSetACLsForGameChannel(self):
self.resetState() self.resetState()
mumble_server = self.mserv 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) self.s.setACLsForGameChannel(mumble_server, cid, game)
acls = mumble_server.channels[cid].acls acls = mumble_server.channels[cid].acls
self.checkACLThings(acls, [{'applyHere' : True, self.checkACLThings(acls, [{'applyHere': True,
'applySubs' : True, 'applySubs': True,
'userid' : -1, 'userid': -1,
'group' : 'all', 'group': 'all',
'deny' : ACLS.ALL, 'deny': ACLS.ALL,
'allow' : 0}, 'allow': 0},
{'applyHere' : True, {'applyHere': True,
'applySubs' : False, 'applySubs': False,
'userid' : -1, 'userid': -1,
'group' : '~source_dod', 'group': '~source_dod',
'deny' : 0, 'deny': 0,
'allow' : ACLS.EAT}]) 'allow': ACLS.EAT}])
def testSetACLsForServerChannel(self): def testSetACLsForServerChannel(self):
self.resetState() self.resetState()
mumble_server = self.mserv 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) self.s.setACLsForServerChannel(mumble_server, cid, game, server)
acls = mumble_server.channels[cid].acls acls = mumble_server.channels[cid].acls
self.checkACLThings(acls, [{'applyHere' : True, self.checkACLThings(acls, [{'applyHere': True,
'applySubs' : False, 'applySubs': False,
'userid' : -1, 'userid': -1,
'group' : '~source_tf_[A-1:SomeServer]', 'group': '~source_tf_[A-1:SomeServer]',
'deny' : 0, 'deny': 0,
'allow' : ACLS.EAT}]) 'allow': ACLS.EAT}])
def testSetACLsForTeamChannel(self): def testSetACLsForTeamChannel(self):
self.resetState() self.resetState()
mumble_server = self.mserv 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) self.s.setACLsForTeamChannel(mumble_server, cid, game, server, team)
acls = mumble_server.channels[cid].acls acls = mumble_server.channels[cid].acls
self.checkACLThings(acls, [{'applyHere' : True, self.checkACLThings(acls, [{'applyHere': True,
'applySubs' : False, 'applySubs': False,
'userid' : -1, 'userid': -1,
'group' : '~source_tf_[A-1:SomeServer]_2', 'group': '~source_tf_[A-1:SomeServer]_2',
'deny' : 0, 'deny': 0,
'allow' : ACLS.ALL}]) 'allow': ACLS.ALL}])
def testAddToGroups(self): def testAddToGroups(self):
self.resetState() self.resetState()
mumble_server = self.mserv mumble_server = self.mserv
prev = mumble_server._lastChannelID() 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) self.s.getOrCreateChannelFor(mumble_server, game, server, team)
# Test # Test
self.s.addToGroups(mumble_server, session, game, server, team) self.s.addToGroups(mumble_server, session, game, server, team)
groups = mumble_server.channels[prev + 1].groups[session] groups = mumble_server.channels[prev + 1].groups[session]
self.assertIn("source_cstrike", groups) self.assertIn("source_cstrike", groups)
self.assertIn("source_cstrike_[A-1:12345]", groups) self.assertIn("source_cstrike_[A-1:12345]", groups)
self.assertIn("source_cstrike_[A-1:12345]_1", groups) self.assertIn("source_cstrike_[A-1:12345]_1", groups)
def testChannelNameMapping(self): def testChannelNameMapping(self):
self.resetState() self.resetState()
mumble_server = self.mserv 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) self.s.getOrCreateChannelFor(mumble_server, game, server, team)
cids = [] cids = []
for c in mumble_server.channels.itervalues(): for c in mumble_server.channels.values():
c.name = str(c.id) c.name = str(c.id)
self.s.channelStateChanged(mumble_server, c) self.s.channelStateChanged(mumble_server, c)
cids.append(c.id) cids.append(c.id)
mumble_server._reset() mumble_server._reset()
self.s.validateChannelDB() self.s.validateChannelDB()
self.assertEqual(len(mumble_server.channels), 0) self.assertEqual(len(mumble_server.channels), 0)
self.assertEqual(len(self.s.db.registeredChannels()), 0) self.assertEqual(len(self.s.db.registeredChannels()), 0)
self.s.getOrCreateChannelFor(mumble_server, game, server, team) self.s.getOrCreateChannelFor(mumble_server, game, server, team)
for cid in cids: for cid in cids:
self.assertEqual(mumble_server._getChan(cid).name, str(cid)) self.assertEqual(mumble_server._getChan(cid).name, str(cid))
if __name__ == "__main__": if __name__ == "__main__":
#logging.basicConfig(level = logging.DEBUG) # logging.basicConfig(level = logging.DEBUG)
#import sys;sys.argv = ['', 'Test.testName'] # import sys;sys.argv = ['', 'Test.testName']
unittest.main() unittest.main()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -34,31 +34,32 @@ class User(object):
User to hold state as well as parsed data fields in a User to hold state as well as parsed data fields in a
sane fashion. sane fashion.
""" """
def __init__(self, state, identity=None, game=None, server=None): def __init__(self, state, identity=None, game=None, server=None):
self.state = state self.state = state
self.identity = identity or {} self.identity = identity or {}
self.server = server self.server = server
self.game = game self.game = game
def valid(self): def valid(self):
""" """
True if valid data is available for all fields True if valid data is available for all fields
""" """
return self.state and self.identity and self.server and self.game return self.state and self.identity and self.server and self.game
def hasContextOrIdentityChanged(self, otherstate): def hasContextOrIdentityChanged(self, otherstate):
""" """
Checks whether the given state diverges from this users's Checks whether the given state diverges from this users's
""" """
return self.state.context != otherstate.context or \ return self.state.context != otherstate.context or \
self.state.identity != otherstate.identity self.state.identity != otherstate.identity
def updateState(self, state): def updateState(self, state):
""" """
Updates the state of this user Updates the state of this user
""" """
self.state = state self.state = state
def updateData(self, identity, game, server): def updateData(self, identity, game, server):
""" """
Updates the data fields for this user Updates the data fields for this user
@@ -66,16 +67,17 @@ class User(object):
self.identity = identity self.identity = identity
self.game = game self.game = game
self.server = server self.server = server
class UserRegistry(object): class UserRegistry(object):
""" """
Registry to store User objects for given servers Registry to store User objects for given servers
and sessions. and sessions.
""" """
def __init__(self): def __init__(self):
self.users = {} # {session:user, ...} self.users = {} # {session:user, ...}
def get(self, sid, session): def get(self, sid, session):
""" """
Return user or None from registry Return user or None from registry
@@ -84,34 +86,34 @@ class UserRegistry(object):
return self.users[sid][session] return self.users[sid][session]
except KeyError: except KeyError:
return None return None
def add(self, sid, session, user): def add(self, sid, session, user):
""" """
Add new user to registry Add new user to registry
""" """
assert(isinstance(user, User)) assert (isinstance(user, User))
if not sid in self.users: if not sid in self.users:
self.users[sid] = {session:user} self.users[sid] = {session: user}
elif not session in self.users[sid]: elif not session in self.users[sid]:
self.users[sid][session] = user self.users[sid][session] = user
else: else:
return False return False
return True return True
def addOrUpdate(self, sid, session, user): def addOrUpdate(self, sid, session, user):
""" """
Add user or overwrite existing one (identified by sid + session) Add user or overwrite existing one (identified by sid + session)
""" """
assert(isinstance(user, User)) assert (isinstance(user, User))
if not sid in self.users: if not sid in self.users:
self.users[sid] = {session:user} self.users[sid] = {session: user}
else: else:
self.users[sid][session] = user self.users[sid][session] = user
return True return True
def remove(self, sid, session): def remove(self, sid, session):
""" """
Remove user from registry Remove user from registry
@@ -120,15 +122,14 @@ class UserRegistry(object):
del self.users[sid][session] del self.users[sid][session]
except KeyError: except KeyError:
return False return False
return True return True
def usingChannel(self, sid, cid): def usingChannel(self, sid, cid):
""" """
Return true if any user in the registry is occupying the given channel 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: if user.state and user.state.channel == cid:
return True return True
return False
return False

View File

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

View File

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

147
mumo.py
View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010-2013 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2010-2013 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,50 +29,51 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import logging
import os import os
import sys import sys
import tempfile
from logging import (debug,
info,
warning,
error,
critical,
exception,
getLogger)
from optparse import OptionParser
from threading import Timer
import Ice import Ice
import IcePy import IcePy
import logging
import tempfile
from config import (Config,
x2bool,
commaSeperatedIntegers)
from threading import Timer from config import (Config,
from optparse import OptionParser commaSeperatedIntegers)
from logging import (debug,
info,
warning,
error,
critical,
exception,
getLogger)
from mumo_manager import MumoManager from mumo_manager import MumoManager
# #
#--- Default configuration values # --- Default configuration values
# #
cfgfile = 'mumo.ini' cfgfile = 'mumo.ini'
default = MumoManager.cfg_default.copy() 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), ('port', int, 6502),
('slice', str, ''), ('slice', str, ''),
('secret', str, ''), ('secret', str, ''),
('slicedirs', str, '/usr/share/slice;/usr/share/Ice/slice'), ('slicedirs', str, '/usr/share/slice;/usr/share/Ice/slice'),
('watchdog', int, 30), ('watchdog', int, 30),
('callback_host', str, '127.0.0.1'), ('callback_host', str, '127.0.0.1'),
('callback_port', int, -1)), ('callback_port', int, -1)),
'iceraw': None,
'murmur': (('servers', commaSeperatedIntegers, []),),
'system': (('pidfile', str, 'mumo.pid'),),
'log': (('level', int, logging.DEBUG),
('file', str, 'mumo.log'))})
'iceraw':None,
'murmur':(('servers', commaSeperatedIntegers, []),),
'system':(('pidfile', str, 'mumo.pid'),),
'log':(('level', int, logging.DEBUG),
('file', str, 'mumo.log'))})
def load_slice(slice): 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 # This function works around a number of differences between Ice python
# versions and distributions when it comes to slice include directories. # versions and distributions when it comes to slice include directories.
# #
@@ -89,9 +90,10 @@ def load_slice(slice):
Ice.loadSlice('', slicedirs + [slice]) Ice.loadSlice('', slicedirs + [slice])
def dynload_slice(prx): 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") info("Loading slice from server")
try: try:
@@ -99,23 +101,25 @@ def dynload_slice(prx):
# In case it breaks with future versions use slice2py and search for # In case it breaks with future versions use slice2py and search for
# "IcePy.Operation('getSlice'," for updates in the generated bindings. # "IcePy.Operation('getSlice'," for updates in the generated bindings.
op = None op = None
if IcePy.intVersion() < 30500L: if IcePy.intVersion() < 30500:
# Old 3.4 signature with 9 parameters # 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: else:
# New 3.5 signature with 10 parameters. # 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)) slice = op.invoke(prx, ((), None))
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix = '.ice') (dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix='.ice')
dynslicefile = os.fdopen(dynslicefiledesc, 'w') dynslicefile = os.fdopen(dynslicefiledesc, 'w')
dynslicefile.write(slice) dynslicefile.write(slice)
dynslicefile.flush() dynslicefile.flush()
load_slice(dynslicefilepath) load_slice(dynslicefilepath)
dynslicefile.close() dynslicefile.close()
os.remove(dynslicefilepath) os.remove(dynslicefilepath)
except Exception, e: except Exception as e:
error("Retrieving slice from server failed") error("Retrieving slice from server failed")
exception(e) exception(e)
raise raise
@@ -123,14 +127,15 @@ def dynload_slice(prx):
def fsload_slice(slice): def fsload_slice(slice):
# #
#--- Load slice from file system # --- Load slice from file system
# #
debug("Loading slice from filesystem: %s" % slice) debug("Loading slice from filesystem: %s" % slice)
load_slice(slice) load_slice(slice)
def do_main_program(): def do_main_program():
# #
#--- Moderator implementation # --- Moderator implementation
# All of this has to go in here so we can correctly daemonize the tool # 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 # without loosing the file descriptors opened by the Ice module
@@ -153,6 +158,7 @@ def do_main_program():
else: else:
fsload_slice(cfg.ice.slice) fsload_slice(cfg.ice.slice)
# noinspection PyUnresolvedReferences
import Murmur import Murmur
class mumoIceApp(Ice.Application): class mumoIceApp(Ice.Application):
@@ -201,7 +207,8 @@ def do_main_program():
else: else:
cbp = '' 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() adapter.activate()
self.adapter = adapter self.adapter = adapter
self.manager.setClientAdapter(adapter) self.manager.setClientAdapter(adapter)
@@ -230,11 +237,12 @@ def do_main_program():
servercb = Murmur.ServerCallbackPrx.uncheckedCast(servercbprx) servercb = Murmur.ServerCallbackPrx.uncheckedCast(servercbprx)
server.addCallback(servercb) 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): if isinstance(e, Ice.ConnectionRefusedException):
error('Server refused connection') error('Server refused connection')
elif isinstance(e, Murmur.InvalidSecretException) or \ 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') error('Invalid ice secret')
else: else:
# We do not actually want to handle this one, re-raise it # 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 Tries to retrieve the server uptime to determine wheter the server is
still responsive or has restarted in the meantime still responsive or has restarted in the meantime
""" """
#debug('Watchdog run') # debug('Watchdog run')
try: try:
uptime = self.meta.getUptime() uptime = self.meta.getUptime()
if self.metaUptime > 0: if self.metaUptime > 0:
@@ -266,8 +274,9 @@ def do_main_program():
self.attachCallbacks() self.attachCallbacks()
self.metaUptime = uptime self.metaUptime = uptime
except Ice.Exception, e: except Ice.Exception as e:
error('Connection to server lost, will try to reestablish callbacks in next watchdog run (%ds)', cfg.ice.watchdog) error('Connection to server lost, will try to reestablish callbacks in next watchdog run (%ds)',
cfg.ice.watchdog)
debug(str(e)) debug(str(e))
self.attachCallbacks() self.attachCallbacks()
@@ -306,11 +315,12 @@ def do_main_program():
The default is to catch all non-Ice exceptions. The default is to catch all non-Ice exceptions.
""" """
def newdec(func): def newdec(func):
def newfunc(*args, **kws): def newfunc(*args, **kws):
try: try:
return func(*args, **kws) return func(*args, **kws)
except Exception, e: except Exception as e:
catch = True catch = True
for ex in exceptions: for ex in exceptions:
if isinstance(e, ex): if isinstance(e, ex):
@@ -324,6 +334,7 @@ def do_main_program():
raise raise
return newfunc return newfunc
return newdec return newdec
class metaCallback(Murmur.MetaCallback): class metaCallback(Murmur.MetaCallback):
@@ -347,7 +358,7 @@ def do_main_program():
server.addCallback(servercb) server.addCallback(servercb)
# Apparently this server was restarted without us noticing # 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": if hasattr(e, "unknown") and e.unknown != "Murmur::InvalidSecretException":
# Special handling for Murmur 1.2.2 servers with invalid slice files # Special handling for Murmur 1.2.2 servers with invalid slice files
raise e raise e
@@ -382,10 +393,10 @@ def do_main_program():
debug('Server shutdown stopped a virtual server') debug('Server shutdown stopped a virtual server')
def forwardServer(fu): def forwardServer(fu):
def new_fu(self, *args, **kwargs): def new_fu(self, *args, **kwargs):
self.manager.announceServer(self.sid, fu.__name__, self.server, *args, **kwargs) self.manager.announceServer(self.sid, fu.__name__, self.server, *args, **kwargs)
return new_fu return new_fu
class serverCallback(Murmur.ServerCallback): class serverCallback(Murmur.ServerCallback):
@@ -405,24 +416,30 @@ def do_main_program():
@checkSecret @checkSecret
@forwardServer @forwardServer
def userStateChanged(self, u, current=None): pass def userStateChanged(self, u, current=None): pass
@checkSecret @checkSecret
@forwardServer @forwardServer
def userDisconnected(self, u, current=None): pass def userDisconnected(self, u, current=None): pass
@checkSecret @checkSecret
@forwardServer @forwardServer
def userConnected(self, u, current=None): pass def userConnected(self, u, current=None): pass
@checkSecret @checkSecret
@forwardServer @forwardServer
def channelCreated(self, c, current=None): pass def channelCreated(self, c, current=None): pass
@checkSecret @checkSecret
@forwardServer @forwardServer
def channelRemoved(self, c, current=None): pass def channelRemoved(self, c, current=None): pass
@checkSecret @checkSecret
@forwardServer @forwardServer
def channelStateChanged(self, c, current=None): pass def channelStateChanged(self, c, current=None): pass
@checkSecret @checkSecret
@forwardServer @forwardServer
def userTextMessage(self, u, m, current=None) : pass def userTextMessage(self, u, m, current=None): pass
class customContextCallback(Murmur.ServerContextCallback): class customContextCallback(Murmur.ServerContextCallback):
def __init__(self, contextActionCallback, *ctx): def __init__(self, contextActionCallback, *ctx):
@@ -436,7 +453,7 @@ def do_main_program():
self.cb(*(self.ctx + args), **argv) self.cb(*(self.ctx + args), **argv)
# #
#--- Start of moderator # --- Start of moderator
# #
info('Starting mumble moderator') info('Starting mumble moderator')
debug('Initializing manager') debug('Initializing manager')
@@ -454,6 +471,7 @@ def do_main_program():
info('Shutdown complete') info('Shutdown complete')
return state return state
class CustomLogger(Ice.Logger): class CustomLogger(Ice.Logger):
""" """
Logger implementation to pipe Ice log messages into Logger implementation to pipe Ice log messages into
@@ -476,8 +494,9 @@ class CustomLogger(Ice.Logger):
def error(self, message): def error(self, message):
self._log.error(message) self._log.error(message)
# #
#--- Start of program # --- Start of program
# #
if __name__ == '__main__': if __name__ == '__main__':
# Parse commandline options # Parse commandline options
@@ -501,22 +520,21 @@ if __name__ == '__main__':
# Load configuration # Load configuration
try: try:
cfg = Config(option.ini, default) cfg = Config(option.ini, default)
except Exception, e: except Exception as e:
print >> sys.stderr, 'Fatal error, could not load config file from "%s"' % cfgfile print('Fatal error, could not load config file from "%s"' % cfgfile, file=sys.stderr)
print >> sys.stderr, e print(e, file=sys.stderr)
sys.exit(1) sys.exit(1)
# Initialise logger # Initialise logger
if cfg.log.file: if cfg.log.file:
try: try:
logfile = open(cfg.log.file, 'a') logfile = open(cfg.log.file, 'a')
except IOError, e: except IOError as e:
#print>>sys.stderr, str(e) # print>>sys.stderr, str(e)
print >> sys.stderr, 'Fatal error, could not open logfile "%s"' % cfg.log.file print('Fatal error, could not open logfile "%s"' % cfg.log.file, file=sys.stderr)
sys.exit(1) sys.exit(1)
else: else:
logfile = logging.sys.stderr logfile = logging.sys.stdout
if option.verbose: if option.verbose:
level = cfg.log.level level = cfg.log.level
@@ -531,16 +549,17 @@ if __name__ == '__main__':
# unless the user explicitly defined what he expected with the -a / -d parameter. # unless the user explicitly defined what he expected with the -a / -d parameter.
try: try:
if option.force_app: if option.force_app:
raise ImportError # Pretend that we couldn't import the daemon lib raise ImportError # Pretend that we couldn't import the daemon lib
import daemon import daemon
try: try:
from daemon.pidfile import TimeoutPIDLockFile from daemon.pidfile import TimeoutPIDLockFile
except ImportError: # Version < 1.6 except ImportError: # Version < 1.6
from daemon.pidlockfile import TimeoutPIDLockFile from daemon.pidlockfile import TimeoutPIDLockFile
except ImportError: except ImportError:
if option.force_daemon: if option.force_daemon:
print >> sys.stderr, 'Fatal error, could not daemonize process due to missing "daemon" library, ' \ print('Fatal error, could not daemonize process due to missing "daemon" library, '
'please install the missing dependency and restart the application' 'please install the missing dependency and restart the application', file=sys.stderr)
sys.exit(1) sys.exit(1)
ret = do_main_program() ret = do_main_program()
else: else:
@@ -548,10 +567,10 @@ if __name__ == '__main__':
if pidfile.is_locked(): if pidfile.is_locked():
try: try:
os.kill(pidfile.read_pid(), 0) 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) sys.exit(1)
except OSError: 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() pidfile.break_lock()
context = daemon.DaemonContext(working_directory=sys.path[0], 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 # -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net> # 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 # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # 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 os
import queue
import sys
import uuid import uuid
from config import Config
from worker import Worker, local_thread, local_thread_blocking
class FailedLoadModuleException(Exception): class FailedLoadModuleException(Exception):
pass pass
class FailedLoadModuleConfigException(FailedLoadModuleException): class FailedLoadModuleConfigException(FailedLoadModuleException):
pass pass
class FailedLoadModuleImportException(FailedLoadModuleException): class FailedLoadModuleImportException(FailedLoadModuleException):
pass pass
class FailedLoadModuleInitializationException(FailedLoadModuleException): class FailedLoadModuleInitializationException(FailedLoadModuleException):
pass pass
def debug_log(enable = True):
def debug_log(enable=True):
def new_dec(fu): def new_dec(fu):
def new_fu(*args, **kwargs): def new_fu(*args, **kwargs):
self = args[0] self = args[0]
log = self.log() 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)) sargs = ','.join([str(arg) for arg in args[1:]]) + '' if not skwargs else (',' + str(skwargs))
call = "%s(%s)" % (fu.__name__, sargs) call = "%s(%s)" % (fu.__name__, sargs)
@@ -61,13 +67,15 @@ def debug_log(enable = True):
res = fu(*args, **kwargs) res = fu(*args, **kwargs)
log.debug("%s -> %s", call, repr(res)) log.debug("%s -> %s", call, repr(res))
return res return res
return new_fu if enable else fu return new_fu if enable else fu
return new_dec return new_dec
debug_me = True debug_me = True
class MumoManagerRemote(object): class MumoManagerRemote(object):
""" """
Manager object handed to MumoModules. This module Manager object handed to MumoModules. This module
@@ -76,7 +84,7 @@ class MumoManagerRemote(object):
as do other signaling to the master MumoManager. as do other signaling to the master MumoManager.
""" """
SERVERS_ALL = [-1] ## Applies to all servers SERVERS_ALL = [-1] ## Applies to all servers
def __init__(self, master, name, queue): def __init__(self, master, name, queue):
self.__master = master self.__master = master
@@ -88,7 +96,7 @@ class MumoManagerRemote(object):
def getQueue(self): def getQueue(self):
return self.__queue 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 Subscribe to meta callbacks. Subscribes the given handler to the following
callbacks: callbacks:
@@ -102,7 +110,7 @@ class MumoManagerRemote(object):
""" """
return self.__master.subscribeMetaCallbacks(self.__queue, handler, servers) 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 Unsubscribe from meta callbacks. Unsubscribes the given handler from callbacks
for the given servers. for the given servers.
@@ -113,7 +121,7 @@ class MumoManagerRemote(object):
""" """
return self.__master.unscubscribeMetaCallbacks(self.__queue, handler, servers) 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 Subscribe to server callbacks. Subscribes the given handler to the following
callbacks: callbacks:
@@ -131,7 +139,7 @@ class MumoManagerRemote(object):
""" """
return self.__master.subscribeServerCallbacks(self.__queue, handler, servers) 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 Unsubscribe from server callbacks. Unsubscribes the given handler from callbacks
for the given servers. for the given servers.
@@ -171,7 +179,8 @@ class MumoManagerRemote(object):
@param action: Action identifier passed to your callback (see above) @param action: Action identifier passed to your callback (see above)
@param text: Text for the menu entry @param text: Text for the menu entry
@param handler: Handler function to call when the menu item is used @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()) server_actions = self.__context_callbacks.get(server.id())
@@ -240,22 +249,22 @@ class MumoManagerRemote(object):
class MumoManager(Worker): class MumoManager(Worker):
MAGIC_ALL = -1 MAGIC_ALL = -1
cfg_default = {'modules':(('mod_dir', str, "modules/"), cfg_default = {'modules': (('mod_dir', str, "modules/"),
('cfg_dir', str, "modules-enabled/"), ('cfg_dir', str, "modules-enabled/"),
('timeout', int, 2))} ('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") Worker.__init__(self, "MumoManager")
self.queues = {} # {queue:module} self.queues = {} # {queue:module}
self.modules = {} # {name:module} self.modules = {} # {name:module}
self.imports = {} # {name:import} self.imports = {} # {name:import}
self.cfg = cfg self.cfg = cfg
self.murmur = murmur self.murmur = murmur
self.meta = None self.meta = None
self.client_adapter = None self.client_adapter = None
self.metaCallbacks = {} # {sid:{queue:[handler]}} self.metaCallbacks = {} # {sid:{queue:[handler]}}
self.serverCallbacks = {} self.serverCallbacks = {}
self.context_callback_type = context_callback_type self.context_callback_type = context_callback_type
@@ -279,13 +288,13 @@ class MumoManager(Worker):
else: else:
mdict[server][queue] = [handler] mdict[server][queue] = [handler]
else: else:
mdict[server] = {queue:[handler]} mdict[server] = {queue: [handler]}
def __rem_from_dict(self, mdict, queue, handler, servers): def __rem_from_dict(self, mdict, queue, handler, servers):
for server in servers: for server in servers:
try: try:
mdict[server][queue].remove(handler) mdict[server][queue].remove(handler)
except KeyError, ValueError: except KeyError as ValueError:
pass pass
def __announce_to_dict(self, mdict, server, function, *args, **kwargs): 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 # Announce to all handlers of the given serverlist
if server == self.MAGIC_ALL: if server == self.MAGIC_ALL:
servers = mdict.iterkeys() servers = iter(mdict.keys())
else: else:
servers = [self.MAGIC_ALL, server] servers = [self.MAGIC_ALL, server]
for server in servers: for server in servers:
try: try:
for queue, handlers in mdict[server].iteritems(): for queue, handlers in mdict[server].items():
for handler in handlers: for handler in handlers:
self.__call_remote(queue, handler, function, *args, **kwargs) self.__call_remote(queue, handler, function, *args, **kwargs)
except KeyError: except KeyError:
@@ -317,31 +326,31 @@ class MumoManager(Worker):
def __call_remote(self, queue, handler, function, *args, **kwargs): def __call_remote(self, queue, handler, function, *args, **kwargs):
try: try:
func = getattr(handler, function) # Find out what to call on target func = getattr(handler, function) # Find out what to call on target
queue.put((None, func, args, kwargs)) queue.put((None, func, args, kwargs))
except AttributeError, e: except AttributeError as e:
mod = self.queues.get(queue, None) mod = self.queues.get(queue, None)
myname = "" myname = ""
for name, mymod in self.modules.iteritems(): for name, mymod in self.modules.items():
if mod == mymod: if mod == mymod:
myname = name myname = name
if myname: 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: else:
self.log().exception(e) self.log().exception(e)
# #
#-- Module multiplexing functionality # -- Module multiplexing functionality
# #
@local_thread @local_thread
def announceConnected(self, meta = None): def announceConnected(self, meta=None):
""" """
Call connected handler on all handlers Call connected handler on all handlers
""" """
self.meta = meta self.meta = meta
for queue, module in self.queues.iteritems(): for queue, module in self.queues.items():
self.__call_remote(queue, module, "connected") self.__call_remote(queue, module, "connected")
@local_thread @local_thread
@@ -349,7 +358,7 @@ class MumoManager(Worker):
""" """
Call disconnected handler on all handlers 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") self.__call_remote(queue, module, "disconnected")
@local_thread @local_thread
@@ -377,7 +386,7 @@ class MumoManager(Worker):
self.__announce_to_dict(self.serverCallbacks, server, function, *args, **kwargs) self.__announce_to_dict(self.serverCallbacks, server, function, *args, **kwargs)
# #
#--- Module self management functionality # --- Module self management functionality
# #
@local_thread @local_thread
@@ -439,11 +448,11 @@ class MumoManager(Worker):
""" """
return self.meta return self.meta
#--- Module load/start/stop/unload functionality # --- Module load/start/stop/unload functionality
# #
@local_thread_blocking @local_thread_blocking
@debug_log(debug_me) @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. Loads a list of modules from the mumo directory structure by name.
@@ -469,30 +478,30 @@ class MumoManager(Worker):
for name in names: for name in names:
try: try:
modinst = self._loadModule_noblock(name) modinst = self._loadModule_noblock(name)
loadedmodules[name] = modinst loadedmodules[name] = modinst
except FailedLoadModuleException: except FailedLoadModuleException:
pass pass
return loadedmodules return loadedmodules
@local_thread_blocking @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) return self._loadModuleCls_noblock(name, modcls, module_cfg)
@debug_log(debug_me) @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() log = self.log()
if name in self.modules: if name in self.modules:
log.error("Module '%s' already loaded", name) log.error("Module '%s' already loaded", name)
return return
modqueue = Queue.Queue() modqueue = queue.Queue()
modmanager = MumoManagerRemote(self, name, modqueue) modmanager = MumoManagerRemote(self, name, modqueue)
try: try:
modinst = modcls(name, modmanager, module_cfg) modinst = modcls(name, modmanager, module_cfg)
except Exception, e: except Exception as e:
msg = "Module '%s' failed to initialize" % name msg = "Module '%s' failed to initialize" % name
log.error(msg) log.error(msg)
log.exception(e) log.exception(e)
@@ -543,14 +552,14 @@ class MumoManager(Worker):
try: try:
mod = __import__(name) mod = __import__(name)
self.imports[name] = mod self.imports[name] = mod
except ImportError, e: except ImportError as e:
msg = "Failed to import module '%s', reason: %s" % (name, str(e)) msg = "Failed to import module '%s', reason: %s" % (name, str(e))
log.error(msg) log.error(msg)
raise FailedLoadModuleImportException(msg) raise FailedLoadModuleImportException(msg)
try: try:
try: try:
modcls = mod.mumo_module_class # First check if there's a magic mumo_module_class variable modcls = mod.mumo_module_class # First check if there's a magic mumo_module_class variable
log.debug("Magic mumo_module_class found") log.debug("Magic mumo_module_class found")
except AttributeError: except AttributeError:
modcls = getattr(mod, name) modcls = getattr(mod, name)
@@ -563,7 +572,7 @@ class MumoManager(Worker):
@local_thread_blocking @local_thread_blocking
@debug_log(debug_me) @debug_log(debug_me)
def startModules(self, names = None): def startModules(self, names=None):
""" """
Start a module by name Start a module by name
@@ -575,12 +584,12 @@ class MumoManager(Worker):
if not names: if not names:
# If no names are given start all models # If no names are given start all models
names = self.modules.iterkeys() names = iter(self.modules.keys())
for name in names: for name in names:
try: try:
modinst = self.modules[name] modinst = self.modules[name]
if not modinst.isAlive(): if not modinst.is_alive():
modinst.start() modinst.start()
log.debug("Module '%s' started", name) log.debug("Module '%s' started", name)
else: else:
@@ -593,7 +602,7 @@ class MumoManager(Worker):
@local_thread_blocking @local_thread_blocking
@debug_log(debug_me) @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 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 for well behaved modules. At this point if a module is really going
@@ -608,7 +617,7 @@ class MumoManager(Worker):
if not names: if not names:
# If no names are given start all models # If no names are given start all models
names = self.modules.iterkeys() names = iter(self.modules.keys())
for name in names: for name in names:
try: try:
@@ -620,29 +629,29 @@ class MumoManager(Worker):
if force: if force:
# We will have to drain the modules queues # 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: if module in self.modules:
try: try:
while queue.get_nowait(): pass while queue.get_nowait(): pass
except Queue.Empty: pass except queue.Empty:
pass
for modinst in stoppedmodules.itervalues(): for modinst in stoppedmodules.values():
if modinst.isAlive(): if modinst.is_alive():
modinst.stop() modinst.stop()
log.debug("Module '%s' is being stopped", name) log.debug("Module '%s' is being stopped", name)
else: else:
log.debug("Module '%s' already stopped", name) log.debug("Module '%s' already stopped", name)
for modinst in stoppedmodules.itervalues(): for modinst in stoppedmodules.values():
modinst.join(timeout = self.cfg.modules.timeout) modinst.join(timeout=self.cfg.modules.timeout)
return stoppedmodules return stoppedmodules
def stop(self, force = True): def stop(self, force=True):
""" """
Stops all modules and shuts down the manager. Stops all modules and shuts down the manager.
""" """
self.log().debug("Stopping") self.log().debug("Stopping")
self.stopModules() self.stopModules()
Worker.stop(self, force) Worker.stop(self, force)

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -30,155 +30,154 @@
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
import unittest import unittest
import Queue from logging import getLogger
from mumo_manager import MumoManager, MumoManagerRemote
from mumo_module import MumoModule
from logging import basicConfig, ERROR, getLogger
import logging
from threading import Event from threading import Event
from mumo_manager import MumoManager
from mumo_module import MumoModule
class MumoManagerTest(unittest.TestCase): class MumoManagerTest(unittest.TestCase):
def setUp(self): def setUp(self):
l = getLogger("MumoManager") l = getLogger("MumoManager")
l.disabled = True l.disabled = True
class MyModule(MumoModule): class MyModule(MumoModule):
def __init__(self, name, manager, configuration = None): def __init__(self, name, manager, configuration=None):
MumoModule.__init__(self, name, manager, configuration) MumoModule.__init__(self, name, manager, configuration)
self.estarted = Event() self.estarted = Event()
self.estopped = Event() self.estopped = Event()
self.econnected = Event() self.econnected = Event()
self.edisconnected = Event() self.edisconnected = Event()
self.emeta = Event() self.emeta = Event()
self.econtext = Event() self.econtext = Event()
self.eserver = Event() self.eserver = Event()
def onStart(self): def onStart(self):
self.estarted.set() self.estarted.set()
def onStop(self): def onStop(self):
self.estopped.set() self.estopped.set()
def connected(self): def connected(self):
man = self.manager() man = self.manager()
man.subscribeMetaCallbacks(self) man.subscribeMetaCallbacks(self)
man.subscribeServerCallbacks(self) man.subscribeServerCallbacks(self)
man.subscribeContextCallbacks(self)
self.econnected.set() self.econnected.set()
def disconnected(self): def disconnected(self):
self.edisconnected.set() self.edisconnected.set()
def metaCallMe(self, arg1, arg2): def metaCallMe(self, arg1, arg2):
if arg1 == "arg1" and arg2 == "arg2": if arg1 == "arg1" and arg2 == "arg2":
self.emeta.set() self.emeta.set()
def contextCallMe(self, server, arg1, arg2): def contextCallMe(self, server, arg1, arg2):
if arg1 == "arg1" and arg2 == "arg2": if arg1 == "arg1" and arg2 == "arg2":
self.econtext.set() self.econtext.set()
def serverCallMe(self, server, arg1, arg2): def serverCallMe(self, server, arg1, arg2):
if arg1 == "arg1" and arg2 == "arg2": if arg1 == "arg1" and arg2 == "arg2":
self.eserver.set() self.eserver.set()
self.mymod = MyModule self.mymod = MyModule
class conf(object): class conf(object):
pass # Dummy class pass # Dummy class
self.cfg = conf() self.cfg = conf()
self.cfg.test = 10 self.cfg.test = 10
# #
#--- Helpers for independent test env creation # --- Helpers for independent test env creation
# #
def up(self): def up(self):
man = MumoManager(None) man = MumoManager(None, None)
man.start() man.start()
mod = man.loadModuleCls("MyModule", self.mymod, self.cfg) mod = man.loadModuleCls("MyModule", self.mymod, self.cfg)
man.startModules() man.startModules()
return (man, mod) return man, mod
def down(self, man, mod): def down(self, man, mod):
man.stopModules() man.stopModules()
man.stop() man.stop()
man.join(timeout=1) man.join(timeout=1)
# #
#--- Tests # --- Tests
# #
def testModuleStarted(self): def testModuleStarted(self):
man, mod = self.up() man, mod = self.up()
mod.estarted.wait(timeout=1) mod.estarted.wait(timeout=1)
assert(mod.estarted.is_set()) assert (mod.estarted.is_set())
self.down(man, mod) self.down(man, mod)
def testModuleStopStart(self): def testModuleStopStart(self):
man ,mod = self.up() man, mod = self.up()
tos = ["MyModule"] tos = ["MyModule"]
self.assertEquals(list(man.stopModules(tos).iterkeys()), tos) self.assertEqual(list(man.stopModules(tos).keys()), tos)
mod.estopped.wait(timeout=1) mod.estopped.wait(timeout=1)
assert(mod.estopped.is_set()) assert (mod.estopped.is_set())
self.down(man, mod) self.down(man, mod)
def testModuleConnectAndDisconnect(self): def testModuleConnectAndDisconnect(self):
man, mod = self.up() man, mod = self.up()
man.announceConnected() man.announceConnected()
mod.econnected.wait(timeout=1) mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set()) assert (mod.econnected.is_set())
man.announceDisconnected() man.announceDisconnected()
mod.edisconnected.wait(timeout=1) mod.edisconnected.wait(timeout=1)
assert(mod.edisconnected.is_set()) assert (mod.edisconnected.is_set())
self.down(man, mod) self.down(man, mod)
def testMetaCallback(self): def testMetaCallback(self):
man, mod = self.up() man, mod = self.up()
man.announceConnected() man.announceConnected()
mod.econnected.wait(timeout=1) mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set()) assert (mod.econnected.is_set())
man.announceMeta(man.MAGIC_ALL, "metaCallMe", "arg1", arg2 = "arg2") man.announceMeta(man.MAGIC_ALL, "metaCallMe", "arg1", arg2="arg2")
mod.emeta.wait(timeout=1) mod.emeta.wait(timeout=1)
assert(mod.emeta.is_set()) assert (mod.emeta.is_set())
man.announceDisconnected() man.announceDisconnected()
self.down(man, mod) self.down(man, mod)
def testContextCallback(self): # FIXME: Test ContextCallbacks correctly
man, mod = self.up() # def testContextCallback(self):
man.announceConnected() # man, mod = self.up()
mod.econnected.wait(timeout=1) # man.announceConnected()
assert(mod.econnected.is_set()) # mod.econnected.wait(timeout=1)
man.announceContext(man.MAGIC_ALL, "contextCallMe", "server", "arg1", arg2 = "arg2") # assert (mod.econnected.is_set())
mod.econtext.wait(timeout=1) # man.announceContext(man.MAGIC_ALL, "contextCallMe", "server", "arg1", arg2="arg2")
assert(mod.econtext.is_set()) # mod.econtext.wait(timeout=1)
man.announceDisconnected() # assert (mod.econtext.is_set())
self.down(man, mod) # man.announceDisconnected()
# self.down(man, mod)
def testServerCallback(self): def testServerCallback(self):
man, mod = self.up() man, mod = self.up()
man.announceConnected() man.announceConnected()
mod.econnected.wait(timeout=1) mod.econnected.wait(timeout=1)
assert(mod.econnected.is_set()) assert (mod.econnected.is_set())
man.announceServer(man.MAGIC_ALL, "serverCallMe", "server", "arg1", arg2 = "arg2") man.announceServer(man.MAGIC_ALL, "serverCallMe", "server", "arg1", arg2="arg2")
mod.eserver.wait(timeout=1) mod.eserver.wait(timeout=1)
assert(mod.eserver.is_set()) assert (mod.eserver.is_set())
man.announceDisconnected() man.announceDisconnected()
self.down(man, mod) self.down(man, mod)
def tearDown(self): def tearDown(self):
pass pass
if __name__ == "__main__": if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName'] # import sys;sys.argv = ['', 'Test.testName']
unittest.main() unittest.main()

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,73 +29,70 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
from config import (Config, from config import (Config)
x2bool,
commaSeperatedIntegers,
commaSeperatedStrings,
commaSeperatedBool)
from worker import Worker from worker import Worker
class MumoModule(Worker): class MumoModule(Worker):
default_config = {} default_config = {}
def __init__(self, name, manager, configuration = None): def __init__(self, name, manager, configuration=None):
Worker.__init__(self, name, manager.getQueue()) Worker.__init__(self, name, manager.getQueue())
self.__manager = manager self.__manager = manager
if isinstance(configuration, basestring): if isinstance(configuration, str):
# If we are passed a string expect a config file there # If we are passed a string expect a config file there
if configuration: if configuration:
self.__cfg = Config(configuration, self.default_config) self.__cfg = Config(configuration, self.default_config)
elif self.default_config: elif self.default_config:
self.__cfg = Config(default = self.default_config) self.__cfg = Config(default=self.default_config)
else: else:
self.__cfg = None self.__cfg = None
else: else:
# If we aren't passed a string it will be a config object or None # If we aren't passed a string it will be a config object or None
self.__cfg = configuration self.__cfg = configuration
self.log().info("Initialized") self.log().info("Initialized")
#--- Accessors # --- Accessors
def manager(self): def manager(self):
return self.__manager return self.__manager
def cfg(self): def cfg(self):
return self.__cfg return self.__cfg
#--- Module control # --- Module control
def onStart(self): def onStart(self):
self.log().info("Start") self.log().info("Start")
def onStop(self): def onStop(self):
self.log().info("Stop") self.log().info("Stop")
#--- Events # --- Events
def connected(self): def connected(self):
# Called once the Ice connection to the murmur server # Called once the Ice connection to the murmur server
# is established. # is established.
# #
# All event registration should happen here # All event registration should happen here
pass pass
def disconnected(self): def disconnected(self):
# Called once a loss of Ice connectivity is detected. # Called once a loss of Ice connectivity is detected.
# #
pass pass
def logModFu(fu): def logModFu(fu):
def new_fu(self, *args, **kwargs): def new_fu(self, *args, **kwargs):
log = self.log() log = self.log()
argss = '' if len(args)==0 else ',' + ','.join(['"%s"' % str(arg) for arg in args]) 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()) 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) log.debug("%s(%s%s%s)", fu.__name__, str(self), argss, kwargss)
return fu(self, *args, **kwargs) return fu(self, *args, **kwargs)
return new_fu return new_fu

View File

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

View File

@@ -1,2 +1,2 @@
# No real module, just here to keep pydev and its # No real module, just here to keep pydev and its
# test runner happy. # test runner happy.

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -39,251 +39,249 @@ import sys
import tempfile import tempfile
from optparse import OptionParser from optparse import OptionParser
# Default settings
import Ice import Ice
import IcePy import IcePy
# Default settings
if __name__ == "__main__": if __name__ == "__main__":
parser = OptionParser() parser = OptionParser()
parser.add_option('-t', '--target', 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', parser.add_option('-p', '--port',
help = 'Port to connect to', default = "6502") help='Port to connect to', default="6502")
parser.add_option('-b', '--base', 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', parser.add_option('-v', '--vserver',
help = 'Virtual server id', default = '1') help='Virtual server id', default='1')
parser.add_option('-i', '--ice', 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', parser.add_option('-s', '--secret',
help = 'Ice secret', default = '') help='Ice secret', default='')
parser.add_option('-l', '--linkteams', action = 'store_true', parser.add_option('-l', '--linkteams', action='store_true',
help = 'Link teams so opposing players can hear each other', default = False) help='Link teams so opposing players can hear each other', default=False)
parser.add_option('-n', '--name', parser.add_option('-n', '--name',
help = 'Treename', default = 'BF2') help='Treename', default='BF2')
parser.add_option('-o', '--out', default = 'bf2.ini', parser.add_option('-o', '--out', default='bf2.ini',
help = 'File to output configuration to') help='File to output configuration to')
parser.add_option('-d', '--slicedir', 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() (option, args) = parser.parse_args()
host = option.target host = option.target
slicedir = option.slicedir slicedir = option.slicedir
try: try:
port = int(option.port) port = int(option.port)
except ValueError: except ValueError:
print "Port value '%s' is invalid" % option.port print("Port value '%s' is invalid" % option.port)
sys.exit(1) sys.exit(1)
try: try:
basechan = int(option.base) basechan = int(option.base)
if basechan < 0: raise ValueError if basechan < 0: raise ValueError
except ValueError: except ValueError:
print "Base channel value '%s' invalid" % option.base print("Base channel value '%s' invalid" % option.base)
sys.exit(1) sys.exit(1)
try: try:
sid = int(option.vserver) sid = int(option.vserver)
if sid < 1: raise ValueError if sid < 1: raise ValueError
except ValueError: except ValueError:
print "Virtual server id value '%s' invalid" % option.vserver print("Virtual server id value '%s' invalid" % option.vserver)
sys.exit(1) sys.exit(1)
name = option.name name = option.name
prxstr = "Meta:tcp -h %s -p %d -t 1000" % (host, port) prxstr = "Meta:tcp -h %s -p %d -t 1000" % (host, port)
secret = option.secret secret = option.secret
props = Ice.createProperties(sys.argv) props = Ice.createProperties(sys.argv)
props.setProperty("Ice.ImplicitContext", "Shared") props.setProperty("Ice.ImplicitContext", "Shared")
idata = Ice.InitializationData() idata = Ice.InitializationData()
idata.properties = props idata.properties = props
ice = Ice.initialize(idata) ice = Ice.initialize(idata)
prx = ice.stringToProxy(prxstr) prx = ice.stringToProxy(prxstr)
print "Done" print("Done")
def lslice(slf): def lslice(slf):
if not hasattr(Ice, "getSliceDir"): if not hasattr(Ice, "getSliceDir"):
Ice.loadSlice('-I%s %s' % (slicedir, slf)) Ice.loadSlice('-I%s %s' % (slicedir, slf))
else: else:
Ice.loadSlice('', ['-I' + Ice.getSliceDir(), slf]) Ice.loadSlice('', ['-I' + Ice.getSliceDir(), slf])
try: try:
print "Trying to retrieve slice dynamically from server...", print("Trying to retrieve slice dynamically from server...", end=' ')
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, ())
if hasattr(Ice, "getSliceDir"): if hasattr(Ice, "getSliceDir"):
slice = op.invoke(prx, ((), None)) slice = op.invoke(prx, ((), None))
else: else:
slice = op.invoke(prx, (), None) slice = op.invoke(prx, (), None)
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix = '.ice') (dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix='.ice')
dynslicefile = os.fdopen(dynslicefiledesc, 'w') dynslicefile = os.fdopen(dynslicefiledesc, 'w')
dynslicefile.write(slice) dynslicefile.write(slice)
dynslicefile.flush() dynslicefile.flush()
lslice(dynslicefilepath) lslice(dynslicefilepath)
dynslicefile.close() dynslicefile.close()
os.remove(dynslicefilepath) os.remove(dynslicefilepath)
print "Success" print("Success")
except Exception, e: except Exception as e:
print "Failed" print("Failed")
print str(e) print(str(e))
slicefile = option.ice slicefile = option.ice
print "Load slice (%s)..." % slicefile, print("Load slice (%s)..." % slicefile, end=' ')
lslice(slicefile) lslice(slicefile)
print "Done" print("Done")
print "Import dynamically compiled murmur class...", print("Import dynamically compiled murmur class...", end=' ')
import Murmur import Murmur
print "Done"
print "Establish ice connection...", print("Done")
print("Establish ice connection...", end=' ')
if secret: if secret:
print "[protected]...", print("[protected]...", end=' ')
ice.getImplicitContext().put("secret", secret) ice.getImplicitContext().put("secret", secret)
murmur = Murmur.MetaPrx.checkedCast(prx) murmur = Murmur.MetaPrx.checkedCast(prx)
print "Done" print("Done")
print "Get server...", print("Get server...", end=' ')
server = murmur.getServer(sid) server = murmur.getServer(sid)
print "Done (%d)" % sid print("Done (%d)" % sid)
ini = {} ini = {'mumble_server': sid, 'name': name, 'ipport_filter': '.*'}
ini['mumble_server'] = sid
ini['name'] = name print("Creating channel structure:")
ini['ipport_filter'] = '.*'
print "Creating channel structure:"
ACL = Murmur.ACL ACL = Murmur.ACL
EAT = Murmur.PermissionEnter | Murmur.PermissionTraverse EAT = Murmur.PermissionEnter | Murmur.PermissionTraverse
W = Murmur.PermissionWhisper W = Murmur.PermissionWhisper
S = Murmur.PermissionSpeak S = Murmur.PermissionSpeak
print name print(name)
ini['left'] = basechan ini['left'] = basechan
gamechan = server.addChannel(name, basechan) gamechan = server.addChannel(name, basechan)
# Relevant function signatures # Relevant function signatures
# Murmur.ACL(self, applyHere=False, applySubs=False, # Murmur.ACL(self, applyHere=False, applySubs=False,
# inherited=False, userid=0, group='', allow=0, deny=0) # inherited=False, userid=0, group='', allow=0, deny=0)
# server.setACL(self, channelid, acls, groups, inherit, _ctx=None) # server.setACL(self, channelid, acls, groups, inherit, _ctx=None)
# #
server.setACL(gamechan, server.setACL(gamechan,
[ACL(applyHere = True, [ACL(applyHere=True,
applySubs = True, applySubs=True,
userid = -1, userid=-1,
group = 'all', group='all',
deny = EAT | W | S), deny=EAT | W | S),
ACL(applyHere = True, ACL(applyHere=True,
applySubs = True, applySubs=True,
userid = -1, userid=-1,
group = '~bf2_%s_game' % name, group='~bf2_%s_game' % name,
allow = S), allow=S),
ACL(applyHere = True, ACL(applyHere=True,
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = '~bf2_%s_game' % name, group='~bf2_%s_game' % name,
allow = EAT | W)], allow=EAT | W)],
[], True) [], True)
gamechanstate = server.getChannelState(gamechan) gamechanstate = server.getChannelState(gamechan)
teams = { teams = {
"opfor": "Team 1", "opfor": "Team 1",
"blufor": "Team 2" "blufor": "Team 2"
} }
id_to_squad_name = { id_to_squad_name = {
"no": "No Squad", "no": "No Squad",
"first": "Squad 1", "first": "Squad 1",
"second": "Squad 2", "second": "Squad 2",
"third": "Squad 3", "third": "Squad 3",
"fourth": "Squad 4", "fourth": "Squad 4",
"fifth": "Squad 5", "fifth": "Squad 5",
"sixth": "Squad 6", "sixth": "Squad 6",
"seventh": "Squad 7", "seventh": "Squad 7",
"eighth": "Squad 8", "eighth": "Squad 8",
"ninth": "Squad 9" "ninth": "Squad 9"
} }
for team,team_name in teams.items(): for team, team_name in list(teams.items()):
print name + "/" + team_name print(name + "/" + team_name)
cid = server.addChannel(team_name, gamechan) cid = server.addChannel(team_name, gamechan)
teamchanstate = server.getChannelState(cid) teamchanstate = server.getChannelState(cid)
if option.linkteams: if option.linkteams:
gamechanstate.links.append(cid) gamechanstate.links.append(cid)
ini[team] = cid ini[team] = cid
server.setACL(ini[team], server.setACL(ini[team],
[ACL(applyHere = True, [ACL(applyHere=True,
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = '~bf2_team', group='~bf2_team',
allow = EAT | W)], allow=EAT | W)],
[], True) [], True)
print name + "/" + team_name + "/Commander" print(name + "/" + team_name + "/Commander")
cid = server.addChannel("Commander", ini[team]) cid = server.addChannel("Commander", ini[team])
teamchanstate.links.append(cid) teamchanstate.links.append(cid)
ini[team + "_commander"] = cid ini[team + "_commander"] = cid
server.setACL(ini[team + "_commander"], server.setACL(ini[team + "_commander"],
[ACL(applyHere = True, [ACL(applyHere=True,
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = '~bf2_commander', group='~bf2_commander',
allow = EAT | W), allow=EAT | W),
ACL(applyHere = True, ACL(applyHere=True,
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = '~bf2_squad_leader', group='~bf2_squad_leader',
allow = W)], allow=W)],
[], True) [], True)
state = server.getChannelState(ini[team+"_commander"]) state = server.getChannelState(ini[team + "_commander"])
state.position = -1 state.position = -1
server.setChannelState(state) server.setChannelState(state)
for squad,squad_name in id_to_squad_name.items(): for squad, squad_name in list(id_to_squad_name.items()):
print name + "/" + team_name + "/" + squad_name print(name + "/" + team_name + "/" + squad_name)
cid = server.addChannel(squad_name, ini[team]) cid = server.addChannel(squad_name, ini[team])
teamchanstate.links.append(cid) teamchanstate.links.append(cid)
ini[team + "_" + squad + "_squad"] = cid ini[team + "_" + squad + "_squad"] = cid
ini[team + "_" + squad + "_squad_leader"] = ini[team + "_" + squad + "_squad"] ini[team + "_" + squad + "_squad_leader"] = ini[team + "_" + squad + "_squad"]
server.setACL(ini[team + "_" + squad + "_squad"], server.setACL(ini[team + "_" + squad + "_squad"],
[ACL(applyHere = True, [ACL(applyHere=True,
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = '~bf2_%s_squad' % squad, group='~bf2_%s_squad' % squad,
allow = EAT | W), allow=EAT | W),
ACL(applyHere = True, ACL(applyHere=True,
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = '~bf2_commander', group='~bf2_commander',
allow = EAT | W), allow=EAT | W),
ACL(applyHere = True, ACL(applyHere=True,
applySubs = False, applySubs=False,
userid = -1, userid=-1,
group = '~bf2_squad_leader', group='~bf2_squad_leader',
allow = W)], allow=W)],
[], True) [], True)
server.setChannelState(teamchanstate) server.setChannelState(teamchanstate)
server.setChannelState(gamechanstate) 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") f = open(option.out, "w")
print>>f, "; Configuration created by mbf2man\n" print("; Configuration created by mbf2man\n", file=f)
print>>f, "[bf2]\ngamecount = 1\n" print("[bf2]\ngamecount = 1\n", file=f)
print>>f, "[g0]" print("[g0]", file=f)
for key in sorted(ini): for key in sorted(ini):
value = ini[key] value = ini[key]
print>>f, "%s = %s" % (key, value) print("%s = %s" % (key, value), file=f)
f.close()
print "Done"
f.close()
print("Done")

View File

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

View File

@@ -1,4 +1,4 @@
#!/usr/bin/env python2 #!/usr/bin/env python3
# -*- coding: utf-8 # -*- coding: utf-8
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net> # Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
@@ -29,29 +29,28 @@
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # 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 logging
import unittest
from logging import ERROR
from logging.handlers import BufferingHandler
from queue import Queue
from threading import Event from threading import Event
from time import sleep from time import sleep
from worker import Worker, local_thread, local_thread_blocking
class WorkerTest(unittest.TestCase): class WorkerTest(unittest.TestCase):
def setUp(self): def setUp(self):
def set_ev(fu): def set_ev(fu):
def new_fu(*args, **kwargs): def new_fu(*args, **kwargs):
s = args[0] s = args[0]
s.event.set() s.event.set()
s.val = (args, kwargs) s.val = (args, kwargs)
return fu(*args, **kwargs) return fu(*args, **kwargs)
return new_fu return new_fu
class ATestWorker(Worker): class ATestWorker(Worker):
def __init__(self, name, message_queue): def __init__(self, name, message_queue):
Worker.__init__(self, name, message_queue) Worker.__init__(self, name, message_queue)
@@ -59,64 +58,63 @@ class WorkerTest(unittest.TestCase):
self.val = None self.val = None
self.started = False self.started = False
self.stopped = False self.stopped = False
@local_thread @local_thread
@set_ev @set_ev
def echo(self, val): def echo(self, val):
return val return val
@local_thread_blocking @local_thread_blocking
@set_ev @set_ev
def echo_block(self, val): def echo_block(self, val):
return val return val
def onStart(self): def onStart(self):
self.started = True self.started = True
def onStop(self): def onStop(self):
self.stopped = True self.stopped = True
@local_thread @local_thread
def raise_(self, ex): def raise_(self, ex):
raise ex raise ex
@local_thread_blocking @local_thread_blocking
def raise_blocking(self, ex): def raise_blocking(self, ex):
raise ex raise ex
@set_ev @set_ev
def call_me_by_name(self, arg1, arg2): def call_me_by_name(self, arg1, arg2):
return return
def call_me_by_name_blocking(self, arg1, arg2): def call_me_by_name_blocking(self, arg1, arg2):
return arg1, arg2 return arg1, arg2
self.buha = BufferingHandler(10000) self.buha = BufferingHandler(10000)
q = Queue() q = Queue()
self.q = q self.q = q
NAME = "Test" NAME = "Test"
l = logging.getLogger(NAME) l = logging.getLogger(NAME)
self.w = ATestWorker(NAME, q) self.w = ATestWorker(NAME, q)
self.assertEqual(self.w.log(), l) self.assertEqual(self.w.log(), l)
l.propagate = 0 l.propagate = 0
l.addHandler(self.buha) l.addHandler(self.buha)
self.assertFalse(self.w.started) self.assertFalse(self.w.started)
self.w.start() self.w.start()
sleep(0.05) sleep(0.05)
self.assertTrue(self.w.started) self.assertTrue(self.w.started)
def testName(self): def testName(self):
assert(self.w.name() == "Test") assert (self.w.name() == "Test")
def testMessageQueue(self): def testMessageQueue(self):
assert(self.w.message_queue() == self.q) assert (self.w.message_queue() == self.q)
def testLocalThread(self): def testLocalThread(self):
s = "Testing" s = "Testing"
self.w.event.clear() self.w.event.clear()
@@ -124,46 +122,46 @@ class WorkerTest(unittest.TestCase):
self.w.event.wait(5) self.w.event.wait(5)
args, kwargs = self.w.val args, kwargs = self.w.val
assert(args[1] == s) assert (args[1] == s)
def testLocalThreadException(self): def testLocalThreadException(self):
self.buha.flush() self.buha.flush()
self.w.raise_(Exception()) self.w.raise_(Exception())
sleep(0.1) # hard delay sleep(0.1) # hard delay
assert(len(self.buha.buffer) != 0) assert (len(self.buha.buffer) != 0)
assert(self.buha.buffer[0].levelno == ERROR) assert (self.buha.buffer[0].levelno == ERROR)
def testCallByName(self): def testCallByName(self):
self.w.event.clear() self.w.event.clear()
self.w.call_by_name(self.w, "call_me_by_name", "arg1", arg2="arg2") self.w.call_by_name(self.w, "call_me_by_name", "arg1", arg2="arg2")
self.w.event.wait(5) self.w.event.wait(5)
args, kwargs = self.w.val args, kwargs = self.w.val
assert(args[1] == "arg1") assert (args[1] == "arg1")
assert(kwargs["arg2"] == "arg2") assert (kwargs["arg2"] == "arg2")
def testLocalThreadBlocking(self): def testLocalThreadBlocking(self):
s = "Testing" s = "Testing"
assert(s == self.w.echo_block(s)) assert (s == self.w.echo_block(s))
def testLocalThreadExceptionBlocking(self): def testLocalThreadExceptionBlocking(self):
class TestException(Exception): pass class TestException(Exception): pass
self.assertRaises(TestException, self.w.raise_blocking, TestException()) self.assertRaises(TestException, self.w.raise_blocking, TestException())
def testCallByNameBlocking(self): def testCallByNameBlocking(self):
arg1, arg2 = self.w.call_by_name_blocking(self.w, "call_me_by_name_blocking", "arg1", arg2="arg2") arg1, arg2 = self.w.call_by_name_blocking(self.w, "call_me_by_name_blocking", "arg1", arg2="arg2")
assert(arg1 == "arg1") assert (arg1 == "arg1")
assert(arg2 == "arg2") assert (arg2 == "arg2")
def tearDown(self): def tearDown(self):
assert(self.w.stopped == False) assert (self.w.stopped is False)
self.w.stop() self.w.stop()
self.w.join(5) self.w.join(5)
assert(self.w.stopped == True) assert self.w.stopped
if __name__ == "__main__": if __name__ == "__main__":
#import sys;sys.argv = ['', 'Test.testName'] # import sys;sys.argv = ['', 'Test.testName']
unittest.main() unittest.main()