Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
27d0d313d2 |
@ -57,7 +57,7 @@ modules-enabled folder.
|
||||
|
||||
## Requirements
|
||||
mumo requires:
|
||||
* python 2.7*
|
||||
* python >=3.2
|
||||
* python-zeroc-ice
|
||||
* murmur >=1.2.3*
|
||||
|
||||
|
52
config.py
52
config.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,40 +29,42 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import ConfigParser
|
||||
import configparser
|
||||
import types
|
||||
|
||||
|
||||
class Config(object):
|
||||
"""
|
||||
Small abstraction for config loading
|
||||
"""
|
||||
|
||||
def __init__(self, filename = None, default = None):
|
||||
def __init__(self, filename=None, default=None):
|
||||
if (filename and not default) or \
|
||||
(not filename and not default): return
|
||||
|
||||
sections = set(default.iterkeys())
|
||||
(not filename and not default): return
|
||||
|
||||
sections = set(default.keys())
|
||||
if filename:
|
||||
cfg = ConfigParser.RawConfigParser()
|
||||
cfg = configparser.RawConfigParser()
|
||||
cfg.optionxform = str
|
||||
with open(filename) as f:
|
||||
cfg.readfp(f)
|
||||
cfg.read_file(f)
|
||||
sections.update(cfg.sections())
|
||||
|
||||
|
||||
for section in sections:
|
||||
if type(section) == types.FunctionType: continue
|
||||
|
||||
if isinstance(section, types.FunctionType):
|
||||
continue
|
||||
|
||||
match = None
|
||||
for default_section in default.iterkeys():
|
||||
for default_section in default.keys():
|
||||
try:
|
||||
if section == default_section or \
|
||||
(type(default_section) == types.FunctionType and default_section(section)):
|
||||
(isinstance(default_section, types.FunctionType) and default_section(section)):
|
||||
match = default_section
|
||||
break
|
||||
except ValueError:
|
||||
continue
|
||||
|
||||
if match == None:
|
||||
|
||||
if match is None:
|
||||
continue
|
||||
|
||||
optiondefaults = default[match]
|
||||
@ -74,7 +76,7 @@ class Config(object):
|
||||
else:
|
||||
try:
|
||||
self.__dict__[section] = cfg.items(section)
|
||||
except ConfigParser.NoSectionError:
|
||||
except configparser.NoSectionError:
|
||||
self.__dict__[section] = []
|
||||
else:
|
||||
self.__dict__[section] = Config()
|
||||
@ -84,42 +86,46 @@ class Config(object):
|
||||
else:
|
||||
try:
|
||||
self.__dict__[section].__dict__[name] = conv(cfg.get(section, name))
|
||||
except (ValueError, ConfigParser.NoSectionError, ConfigParser.NoOptionError):
|
||||
except (ValueError, configparser.NoSectionError, configparser.NoOptionError):
|
||||
self.__dict__[section].__dict__[name] = vdefault
|
||||
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self.__dict__.__getitem__(key)
|
||||
|
||||
|
||||
def __contains__(self, key):
|
||||
return self.__dict__.__contains__(key)
|
||||
|
||||
|
||||
def x2bool(s):
|
||||
"""
|
||||
Helper function to convert strings from the config to bool
|
||||
"""
|
||||
if isinstance(s, bool):
|
||||
return s
|
||||
elif isinstance(s, basestring):
|
||||
elif isinstance(s, str):
|
||||
return s.strip().lower() in ['1', 'true']
|
||||
raise ValueError()
|
||||
|
||||
|
||||
def commaSeperatedIntegers(s):
|
||||
"""
|
||||
Helper function to convert a string from the config
|
||||
containing comma seperated integers into a list of integers
|
||||
"""
|
||||
return map(int, s.split(','))
|
||||
return list(map(int, s.split(',')))
|
||||
|
||||
|
||||
def commaSeperatedStrings(s):
|
||||
"""
|
||||
Helper function to convert a string from the config
|
||||
containing comma seperated strings into a list of strings
|
||||
"""
|
||||
return map(str.strip, s.split(','))
|
||||
return list(map(str.strip, s.split(',')))
|
||||
|
||||
|
||||
def commaSeperatedBool(s):
|
||||
"""
|
||||
Helper function to convert a string from the config
|
||||
containing comma seperated strings into a list of booleans
|
||||
"""
|
||||
return map(x2bool, s.split(','))
|
||||
return list(map(x2bool, s.split(',')))
|
||||
|
112
config_test.py
112
config_test.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,13 +29,15 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import unittest
|
||||
from config import Config, x2bool, commaSeperatedIntegers, commaSeperatedStrings, commaSeperatedBool
|
||||
from tempfile import mkstemp
|
||||
import os
|
||||
import re
|
||||
import unittest
|
||||
from tempfile import mkstemp
|
||||
|
||||
def create_file(content = None):
|
||||
from config import Config, x2bool, commaSeperatedIntegers, commaSeperatedStrings, commaSeperatedBool
|
||||
|
||||
|
||||
def create_file(content=None):
|
||||
"""
|
||||
Creates a temp file filled with 'content' and returns its path.
|
||||
The file has to be manually deleted later on
|
||||
@ -43,11 +45,12 @@ def create_file(content = None):
|
||||
fd, path = mkstemp()
|
||||
f = os.fdopen(fd, "wb")
|
||||
if content:
|
||||
f.write(content)
|
||||
f.write(content.encode())
|
||||
f.flush()
|
||||
f.close()
|
||||
return path
|
||||
|
||||
|
||||
class ConfigTest(unittest.TestCase):
|
||||
cfg_content = """[world]
|
||||
domination = True
|
||||
@ -62,15 +65,15 @@ value = False
|
||||
[Server_2]
|
||||
value = True
|
||||
"""
|
||||
|
||||
cfg_default = {'world':(('domination', x2bool, False),
|
||||
('somestr', str, "fail"),
|
||||
('somenum', int, 0),
|
||||
('somenumtest', int, 1),
|
||||
('blubber', str, "empty"),
|
||||
('serverregex', re.compile, '.*')),
|
||||
(lambda x: re.match("Server_\d+",x)):(('value', x2bool, True),),
|
||||
'somethingelse':(('bla', str, "test"),)}
|
||||
|
||||
cfg_default = {'world': (('domination', x2bool, False),
|
||||
('somestr', str, "fail"),
|
||||
('somenum', int, 0),
|
||||
('somenumtest', int, 1),
|
||||
('blubber', str, "empty"),
|
||||
('serverregex', re.compile, '.*')),
|
||||
(lambda x: re.match("Server_\d+", x)): (('value', x2bool, True),),
|
||||
'somethingelse': (('bla', str, "test"),)}
|
||||
|
||||
def setUp(self):
|
||||
pass
|
||||
@ -78,76 +81,75 @@ value = True
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
def testEmpty(self):
|
||||
path = create_file()
|
||||
try:
|
||||
cfg = Config(path, self.cfg_default)
|
||||
assert(cfg.world.domination == False)
|
||||
assert(cfg.world.somestr == "fail")
|
||||
assert(cfg.world.somenum == 0)
|
||||
assert (cfg.world.domination == False)
|
||||
assert (cfg.world.somestr == "fail")
|
||||
assert (cfg.world.somenum == 0)
|
||||
self.assertRaises(AttributeError, getattr, cfg.world, "testfallbacknum")
|
||||
assert(cfg.somethingelse.bla == "test")
|
||||
assert (cfg.somethingelse.bla == "test")
|
||||
finally:
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def testX2bool(self):
|
||||
assert(x2bool(" true") == True)
|
||||
assert(x2bool("false") == False)
|
||||
assert(x2bool(" TrUe") == True)
|
||||
assert(x2bool("FaLsE ") == False)
|
||||
assert(x2bool("0 ") == False)
|
||||
assert(x2bool("1") == True)
|
||||
assert(x2bool(" 10") == False)
|
||||
assert(x2bool("notabool") == False)
|
||||
|
||||
assert (x2bool(" true") == True)
|
||||
assert (x2bool("false") == False)
|
||||
assert (x2bool(" TrUe") == True)
|
||||
assert (x2bool("FaLsE ") == False)
|
||||
assert (x2bool("0 ") == False)
|
||||
assert (x2bool("1") == True)
|
||||
assert (x2bool(" 10") == False)
|
||||
assert (x2bool("notabool") == False)
|
||||
|
||||
def testCommaSeperatedIntegers(self):
|
||||
assert(commaSeperatedIntegers(" 1,2 , 333 ") == [1,2,333])
|
||||
assert (commaSeperatedIntegers(" 1,2 , 333 ") == [1, 2, 333])
|
||||
self.assertRaises(ValueError, commaSeperatedIntegers, "1,2,a")
|
||||
|
||||
|
||||
def testCommaSeperatedStrings(self):
|
||||
assert(commaSeperatedStrings("Bernd, the, bred !") == ["Bernd", "the", "bred !"])
|
||||
|
||||
assert (commaSeperatedStrings("Bernd, the, bred !") == ["Bernd", "the", "bred !"])
|
||||
|
||||
def testCommaSeperatedBool(self):
|
||||
assert(commaSeperatedBool("tRue ,false, 0, 0, 1,1, test") == [True, False, False, False, True, True, False])
|
||||
|
||||
assert (commaSeperatedBool("tRue ,false, 0, 0, 1,1, test") == [True, False, False, False, True, True, False])
|
||||
|
||||
def testConfig(self):
|
||||
path = create_file(self.cfg_content)
|
||||
try:
|
||||
try:
|
||||
cfg = Config(path, self.cfg_default)
|
||||
except Exception, e:
|
||||
print e
|
||||
assert(cfg.world.domination == True)
|
||||
assert(cfg.world.somestr == "Blabla")
|
||||
assert(cfg.world.somenum == 10)
|
||||
except Exception as e:
|
||||
print(e)
|
||||
assert (cfg.world.domination == True)
|
||||
assert (cfg.world.somestr == "Blabla")
|
||||
assert (cfg.world.somenum == 10)
|
||||
self.assertRaises(AttributeError, getattr, cfg.world, "testfallbacknum")
|
||||
self.assertEqual(cfg.world.blubber, "Things %(doesnotexistsasdefault)s")
|
||||
self.assertEqual(cfg.world.serverregex, re.compile("^\[[\w\d\-\(\):]{1,20}\]$"))
|
||||
assert(cfg.somethingelse.bla == "test")
|
||||
assert(cfg.Server_10.value == False)
|
||||
assert(cfg.Server_2.value == True)
|
||||
assert(cfg.Server_9.value == True)
|
||||
assert (cfg.somethingelse.bla == "test")
|
||||
assert (cfg.Server_10.value == False)
|
||||
assert (cfg.Server_2.value == True)
|
||||
assert (cfg.Server_9.value == True)
|
||||
finally:
|
||||
os.remove(path)
|
||||
|
||||
|
||||
def testLoadDefault(self):
|
||||
cfg = Config(default=self.cfg_default)
|
||||
assert(cfg.world.domination == False)
|
||||
assert(cfg.somethingelse.bla == "test")
|
||||
assert(cfg.world.somenum == 0)
|
||||
|
||||
assert (cfg.world.domination == False)
|
||||
assert (cfg.somethingelse.bla == "test")
|
||||
assert (cfg.world.somenum == 0)
|
||||
|
||||
def testGetItem(self):
|
||||
cfg = Config(default=self.cfg_default)
|
||||
assert(cfg["world"]["domination"] == False)
|
||||
assert("world" in cfg)
|
||||
|
||||
assert (cfg["world"]["domination"] == False)
|
||||
assert ("world" in cfg)
|
||||
|
||||
def invalidaccess(c):
|
||||
c["nointhisconfig"]
|
||||
|
||||
|
||||
self.assertRaises(KeyError, invalidaccess, cfg)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
# import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
|
@ -17,7 +17,7 @@ DESC="Mumo bot for Mumble"
|
||||
WORKDIR=/opt/mumo
|
||||
PIDDIR=$WORKDIR
|
||||
PIDFILE=$PIDDIR/mumo.pid
|
||||
DAEMON=/usr/bin/python
|
||||
DAEMON=/usr/bin/python3
|
||||
USER=mumo
|
||||
GROUP=mumo
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
# No real module, just here to keep pydev and its
|
||||
# test runner happy.
|
||||
# test runner happy.
|
||||
|
314
modules/bf2.py
314
modules/bf2.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -35,77 +35,75 @@
|
||||
# gamestate reported by Mumble positional audio plugins
|
||||
#
|
||||
|
||||
from mumo_module import (MumoModule,
|
||||
x2bool)
|
||||
|
||||
import json
|
||||
import re
|
||||
try:
|
||||
import json
|
||||
except ImportError: # Fallback for python < 2.6
|
||||
import simplejson as json
|
||||
|
||||
from config import x2bool
|
||||
from mumo_module import MumoModule
|
||||
|
||||
|
||||
class bf2(MumoModule):
|
||||
default_config = {'bf2':(
|
||||
('gamecount', int, 1),
|
||||
),
|
||||
lambda x: re.match('g\d+', x):(
|
||||
('name', str, ''),
|
||||
('mumble_server', int, 1),
|
||||
('ipport_filter_negate', x2bool, False),
|
||||
('ipport_filter', re.compile, re.compile('.*')),
|
||||
|
||||
('base', int, 0),
|
||||
('left', int, -1),
|
||||
|
||||
('blufor', int, -1),
|
||||
('blufor_commander', int, -1),
|
||||
('blufor_no_squad', int, -1),
|
||||
('blufor_first_squad', int, -1),
|
||||
('blufor_first_squad_leader', int, -1),
|
||||
('blufor_second_squad', int, -1),
|
||||
('blufor_second_squad_leader', int, -1),
|
||||
('blufor_third_squad', int, -1),
|
||||
('blufor_third_squad_leader', int, -1),
|
||||
('blufor_fourth_squad', int, -1),
|
||||
('blufor_fourth_squad_leader', int, -1),
|
||||
('blufor_fifth_squad', int, -1),
|
||||
('blufor_fifth_squad_leader', int, -1),
|
||||
('blufor_sixth_squad', int, -1),
|
||||
('blufor_sixth_squad_leader', int, -1),
|
||||
('blufor_seventh_squad', int, -1),
|
||||
('blufor_seventh_squad_leader', int, -1),
|
||||
('blufor_eighth_squad', int, -1),
|
||||
('blufor_eighth_squad_leader', int, -1),
|
||||
('blufor_ninth_squad', int, -1),
|
||||
('blufor_ninth_squad_leader', int, -1),
|
||||
|
||||
('opfor', int, -1),
|
||||
('opfor_commander', int, -1),
|
||||
('opfor_no_squad', int, -1),
|
||||
('opfor_first_squad', int, -1),
|
||||
('opfor_first_squad_leader', int, -1),
|
||||
('opfor_second_squad', int, -1),
|
||||
('opfor_second_squad_leader', int, -1),
|
||||
('opfor_third_squad', int, -1),
|
||||
('opfor_third_squad_leader', int, -1),
|
||||
('opfor_fourth_squad', int, -1),
|
||||
('opfor_fourth_squad_leader', int, -1),
|
||||
('opfor_fifth_squad', int, -1),
|
||||
('opfor_fifth_squad_leader', int, -1),
|
||||
('opfor_sixth_squad', int, -1),
|
||||
('opfor_sixth_squad_leader', int, -1),
|
||||
('opfor_seventh_squad', int, -1),
|
||||
('opfor_seventh_squad_leader', int, -1),
|
||||
('opfor_eighth_squad', int, -1),
|
||||
('opfor_eighth_squad_leader', int, -1),
|
||||
('opfor_ninth_squad', int, -1),
|
||||
('opfor_ninth_squad_leader', int, -1)
|
||||
),
|
||||
}
|
||||
|
||||
default_config = {'bf2': (
|
||||
('gamecount', int, 1),
|
||||
),
|
||||
lambda x: re.match('g\d+', x): (
|
||||
('name', str, ''),
|
||||
('mumble_server', int, 1),
|
||||
('ipport_filter_negate', x2bool, False),
|
||||
('ipport_filter', re.compile, re.compile('.*')),
|
||||
|
||||
('base', int, 0),
|
||||
('left', int, -1),
|
||||
|
||||
('blufor', int, -1),
|
||||
('blufor_commander', int, -1),
|
||||
('blufor_no_squad', int, -1),
|
||||
('blufor_first_squad', int, -1),
|
||||
('blufor_first_squad_leader', int, -1),
|
||||
('blufor_second_squad', int, -1),
|
||||
('blufor_second_squad_leader', int, -1),
|
||||
('blufor_third_squad', int, -1),
|
||||
('blufor_third_squad_leader', int, -1),
|
||||
('blufor_fourth_squad', int, -1),
|
||||
('blufor_fourth_squad_leader', int, -1),
|
||||
('blufor_fifth_squad', int, -1),
|
||||
('blufor_fifth_squad_leader', int, -1),
|
||||
('blufor_sixth_squad', int, -1),
|
||||
('blufor_sixth_squad_leader', int, -1),
|
||||
('blufor_seventh_squad', int, -1),
|
||||
('blufor_seventh_squad_leader', int, -1),
|
||||
('blufor_eighth_squad', int, -1),
|
||||
('blufor_eighth_squad_leader', int, -1),
|
||||
('blufor_ninth_squad', int, -1),
|
||||
('blufor_ninth_squad_leader', int, -1),
|
||||
|
||||
('opfor', int, -1),
|
||||
('opfor_commander', int, -1),
|
||||
('opfor_no_squad', int, -1),
|
||||
('opfor_first_squad', int, -1),
|
||||
('opfor_first_squad_leader', int, -1),
|
||||
('opfor_second_squad', int, -1),
|
||||
('opfor_second_squad_leader', int, -1),
|
||||
('opfor_third_squad', int, -1),
|
||||
('opfor_third_squad_leader', int, -1),
|
||||
('opfor_fourth_squad', int, -1),
|
||||
('opfor_fourth_squad_leader', int, -1),
|
||||
('opfor_fifth_squad', int, -1),
|
||||
('opfor_fifth_squad_leader', int, -1),
|
||||
('opfor_sixth_squad', int, -1),
|
||||
('opfor_sixth_squad_leader', int, -1),
|
||||
('opfor_seventh_squad', int, -1),
|
||||
('opfor_seventh_squad_leader', int, -1),
|
||||
('opfor_eighth_squad', int, -1),
|
||||
('opfor_eighth_squad_leader', int, -1),
|
||||
('opfor_ninth_squad', int, -1),
|
||||
('opfor_ninth_squad_leader', int, -1)
|
||||
),
|
||||
}
|
||||
|
||||
id_to_squad_name = ["no", "first", "second", "third", "fourth", "fifth", "sixth", "seventh", "eighth", "ninth"]
|
||||
|
||||
def __init__(self, name, manager, configuration = None):
|
||||
|
||||
def __init__(self, name, manager, configuration=None):
|
||||
MumoModule.__init__(self, name, manager, configuration)
|
||||
self.murmur = manager.getMurmurModule()
|
||||
|
||||
@ -114,7 +112,7 @@ class bf2(MumoModule):
|
||||
manager = self.manager()
|
||||
log = self.log()
|
||||
log.debug("Register for Server callbacks")
|
||||
|
||||
|
||||
servers = set()
|
||||
for i in range(cfg.bf2.gamecount):
|
||||
try:
|
||||
@ -122,23 +120,24 @@ class bf2(MumoModule):
|
||||
except KeyError:
|
||||
log.error("Invalid configuration. Game configuration for 'g%d' not found.", i)
|
||||
return
|
||||
|
||||
self.sessions = {} # {serverid:{sessionid:laststate}}
|
||||
|
||||
self.sessions = {} # {serverid:{sessionid:laststate}}
|
||||
manager.subscribeServerCallbacks(self, servers)
|
||||
manager.subscribeMetaCallbacks(self, servers)
|
||||
|
||||
def disconnected(self): pass
|
||||
|
||||
|
||||
def disconnected(self):
|
||||
pass
|
||||
|
||||
#
|
||||
#--- Module specific state handling code
|
||||
# --- Module specific state handling code
|
||||
#
|
||||
def update_state(self, server, oldstate, newstate):
|
||||
log = self.log()
|
||||
sid = server.id()
|
||||
|
||||
|
||||
session = newstate.session
|
||||
newoldchannel = newstate.channel
|
||||
|
||||
|
||||
try:
|
||||
opc = oldstate.parsedcontext
|
||||
ogcfgname = opc["gamename"]
|
||||
@ -147,15 +146,15 @@ class bf2(MumoModule):
|
||||
opi = oldstate.parsedidentity
|
||||
except (AttributeError, KeyError):
|
||||
og = None
|
||||
|
||||
|
||||
opi = {}
|
||||
opc = {}
|
||||
|
||||
|
||||
if oldstate and oldstate.is_linked:
|
||||
oli = True
|
||||
else:
|
||||
oli = False
|
||||
|
||||
|
||||
try:
|
||||
npc = newstate.parsedcontext
|
||||
ngcfgname = npc["gamename"]
|
||||
@ -164,23 +163,25 @@ class bf2(MumoModule):
|
||||
npi = newstate.parsedidentity
|
||||
except (AttributeError, KeyError):
|
||||
ng = None
|
||||
|
||||
|
||||
npi = {}
|
||||
npc = {}
|
||||
nli = False
|
||||
|
||||
|
||||
if newstate and newstate.is_linked:
|
||||
nli = True
|
||||
else:
|
||||
nli = False
|
||||
|
||||
|
||||
if not oli and nli:
|
||||
log.debug("User '%s' (%d|%d) on server %d now linked", newstate.name, newstate.session, newstate.userid, sid)
|
||||
log.debug("User '%s' (%d|%d) on server %d now linked", newstate.name, newstate.session, newstate.userid,
|
||||
sid)
|
||||
server.addUserToGroup(0, session, "bf2_linked")
|
||||
|
||||
|
||||
if opi and opc:
|
||||
squadname = self.id_to_squad_name[opi["squad"]]
|
||||
log.debug("Removing user '%s' (%d|%d) on server %d from groups of game %s / squad %s", newstate.name, newstate.session, newstate.userid, sid, og or ogcfgname, squadname)
|
||||
log.debug("Removing user '%s' (%d|%d) on server %d from groups of game %s / squad %s", newstate.name,
|
||||
newstate.session, newstate.userid, sid, og or ogcfgname, squadname)
|
||||
server.removeUserFromGroup(ogcfg["base"], session, "bf2_%s_game" % (og or ogcfgname))
|
||||
server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_commander")
|
||||
server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_squad_leader")
|
||||
@ -189,72 +190,74 @@ class bf2(MumoModule):
|
||||
server.removeUserFromGroup(ogcfg[opi["team"]], session, "bf2_team")
|
||||
channame = "left"
|
||||
newstate.channel = ogcfg["left"]
|
||||
|
||||
|
||||
if npc and npi:
|
||||
log.debug("Updating user '%s' (%d|%d) on server %d in game %s: %s", newstate.name, newstate.session, newstate.userid, sid, ng or ngcfgname, str(npi))
|
||||
|
||||
log.debug("Updating user '%s' (%d|%d) on server %d in game %s: %s", newstate.name, newstate.session,
|
||||
newstate.userid, sid, ng or ngcfgname, str(npi))
|
||||
|
||||
squadname = self.id_to_squad_name[npi["squad"]]
|
||||
|
||||
|
||||
# Add to game group
|
||||
location = "base"
|
||||
group = "bf2_%s_game" % (ng or ngcfgname)
|
||||
server.addUserToGroup(ngcfg[location], session, group)
|
||||
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
|
||||
|
||||
|
||||
# Then add to team group
|
||||
location = npi["team"]
|
||||
group = "bf2_team"
|
||||
server.addUserToGroup(ngcfg[location], session, group)
|
||||
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
|
||||
|
||||
|
||||
# Then add to squad group
|
||||
group = "bf2_%s_squad" % squadname
|
||||
server.addUserToGroup(ngcfg[location], session, group)
|
||||
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"]])
|
||||
newstate.channel = ngcfg[channame]
|
||||
|
||||
|
||||
if npi["squad_leader"]:
|
||||
# In case the leader flag is set add to leader group
|
||||
group = "bf2_%s_squad_leader" % squadname
|
||||
server.addUserToGroup(ngcfg[location], session, group)
|
||||
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
|
||||
|
||||
|
||||
group = "bf2_squad_leader"
|
||||
server.addUserToGroup(ngcfg[location], session, group)
|
||||
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
|
||||
|
||||
|
||||
# Override previous moves
|
||||
channame = "%s_%s_squad_leader" % (npi["team"], self.id_to_squad_name[npi["squad"]])
|
||||
newstate.channel = ngcfg[channame]
|
||||
|
||||
|
||||
if npi["commander"]:
|
||||
group = "bf2_commander"
|
||||
server.addUserToGroup(ngcfg[location], session, group)
|
||||
log.debug("Added '%s' @ %s to group %s in %s", newstate.name, ng or ngcfgname, group, location)
|
||||
|
||||
|
||||
# Override previous moves
|
||||
channame = "%s_commander" % npi["team"]
|
||||
newstate.channel = ngcfg[channame]
|
||||
|
||||
|
||||
if oli and not nli:
|
||||
log.debug("User '%s' (%d|%d) on server %d no longer linked", newstate.name, newstate.session, newstate.userid, sid)
|
||||
log.debug("User '%s' (%d|%d) on server %d no longer linked", newstate.name, newstate.session,
|
||||
newstate.userid, sid)
|
||||
server.removeUserFromGroup(0, session, "bf2_linked")
|
||||
|
||||
if newstate.channel >= 0 and newoldchannel != newstate.channel:
|
||||
if ng == None:
|
||||
|
||||
if 0 <= newstate.channel != newoldchannel:
|
||||
if ng is None:
|
||||
log.debug("Moving '%s' leaving %s to channel %s", newstate.name, og or ogcfgname, channame)
|
||||
else:
|
||||
log.debug("Moving '%s' @ %s to channel %s", newstate.name, ng or ngcfgname, channame)
|
||||
|
||||
|
||||
server.setState(newstate)
|
||||
|
||||
|
||||
def handle(self, server, state):
|
||||
def verify(mdict, key, vtype):
|
||||
if not isinstance(mdict[key], vtype):
|
||||
raise ValueError("'%s' of invalid type" % key)
|
||||
|
||||
|
||||
cfg = self.cfg()
|
||||
log = self.log()
|
||||
sid = server.id()
|
||||
@ -263,17 +266,17 @@ class bf2(MumoModule):
|
||||
state.parsedidentity = {}
|
||||
state.parsedcontext = {}
|
||||
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] = {}
|
||||
|
||||
|
||||
update = False
|
||||
if state.session in self.sessions[sid]:
|
||||
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
|
||||
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.parsedcontext = self.sessions[sid][state.session].parsedcontext
|
||||
state.parsedidentity = self.sessions[sid][state.session].parsedidentity
|
||||
@ -282,46 +285,48 @@ class bf2(MumoModule):
|
||||
# New user with engaged plugin => update
|
||||
self.sessions[sid][state.session] = None
|
||||
update = True
|
||||
|
||||
|
||||
if not update:
|
||||
self.sessions[sid][state.session] = state
|
||||
return
|
||||
|
||||
|
||||
# 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
|
||||
splitcontext = state.context.split('\0', 1)
|
||||
if splitcontext[0] == "Battlefield 2":
|
||||
state.is_linked = True
|
||||
if state.identity and len(splitcontext) == 1:
|
||||
#LEGACY: Assume broken Ice 3.2 which doesn't transmit context after \0
|
||||
splitcontext.append('{"ipport":""}') # Obviously this doesn't give full functionality but it doesn't crash either ;-)
|
||||
# LEGACY: Assume broken Ice 3.2 which doesn't transmit context after \0
|
||||
splitcontext.append(
|
||||
'{"ipport":""}') # Obviously this doesn't give full functionality but it doesn't crash either ;-)
|
||||
|
||||
if state.is_linked and len(splitcontext) == 2 and state.identity:
|
||||
if state.is_linked and len(splitcontext) == 2 and state.identity:
|
||||
try:
|
||||
context = json.loads(splitcontext[1])
|
||||
verify(context, "ipport", basestring)
|
||||
|
||||
verify(context, "ipport", str)
|
||||
|
||||
for i in range(cfg.bf2.gamecount):
|
||||
# Try to find a matching game
|
||||
gamename = "g%d" % i
|
||||
gamecfg = getattr(cfg, gamename)
|
||||
|
||||
|
||||
if gamecfg.mumble_server == server.id():
|
||||
not_matched = (gamecfg.ipport_filter.match(context["ipport"]) == None)
|
||||
not_matched = (gamecfg.ipport_filter.match(context["ipport"]) is None)
|
||||
if not_matched == gamecfg.ipport_filter_negate:
|
||||
break
|
||||
gamename = None
|
||||
|
||||
|
||||
if not gamename:
|
||||
raise ValueError("No matching game found")
|
||||
|
||||
|
||||
context["gamecfg"] = gamecfg
|
||||
context["gamename"] = gamename
|
||||
state.parsedcontext = context
|
||||
|
||||
except (ValueError, KeyError, AttributeError), e:
|
||||
log.debug("Invalid context for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid, sid, repr(e))
|
||||
|
||||
except (ValueError, KeyError, AttributeError) as e:
|
||||
log.debug("Invalid context for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid,
|
||||
sid, repr(e))
|
||||
|
||||
try:
|
||||
identity = json.loads(state.identity)
|
||||
verify(identity, "commander", bool)
|
||||
@ -329,48 +334,57 @@ class bf2(MumoModule):
|
||||
verify(identity, "squad", int)
|
||||
if identity["squad"] < 0 or identity["squad"] > 9:
|
||||
raise ValueError("Invalid squad number")
|
||||
verify(identity, "team", basestring)
|
||||
verify(identity, "team", str)
|
||||
if identity["team"] != "opfor" and identity["team"] != "blufor":
|
||||
raise ValueError("Invalid team identified")
|
||||
#LEGACY: Ice 3.2 cannot handle unicode strings
|
||||
# LEGACY: Ice 3.2 cannot handle unicode strings
|
||||
identity["team"] = str(identity["team"])
|
||||
|
||||
|
||||
state.parsedidentity = identity
|
||||
|
||||
except (KeyError, ValueError), e:
|
||||
log.debug("Invalid identity for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid, sid, repr(e))
|
||||
|
||||
except (KeyError, ValueError) as e:
|
||||
log.debug("Invalid identity for %s (%d|%d) on server %d: %s", state.name, state.session, state.userid,
|
||||
sid, repr(e))
|
||||
|
||||
# Update state and remember it
|
||||
self.update_state(server, self.sessions[sid][state.session], state)
|
||||
self.sessions[sid][state.session] = state
|
||||
|
||||
|
||||
#
|
||||
#--- Server callback functions
|
||||
# --- Server callback functions
|
||||
#
|
||||
|
||||
def userDisconnected(self, server, state, context = None):
|
||||
|
||||
def userDisconnected(self, server, state, context=None):
|
||||
try:
|
||||
sid = server.id()
|
||||
del self.sessions[sid][state.session]
|
||||
except KeyError: pass
|
||||
|
||||
def userStateChanged(self, server, state, context = None):
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
def userStateChanged(self, server, state, context=None):
|
||||
self.handle(server, state)
|
||||
|
||||
def userConnected(self, server, state, context = None):
|
||||
|
||||
def userConnected(self, server, state, context=None):
|
||||
self.handle(server, state)
|
||||
|
||||
def userTextMessage(self, server, user, message, current=None): pass
|
||||
def channelCreated(self, server, state, context = None): pass
|
||||
def channelRemoved(self, server, state, context = None): pass
|
||||
def channelStateChanged(self, server, state, context = None): pass
|
||||
|
||||
|
||||
def userTextMessage(self, server, user, message, current=None):
|
||||
pass
|
||||
|
||||
def channelCreated(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelRemoved(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelStateChanged(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
#
|
||||
#--- Meta callback functions
|
||||
# --- Meta callback functions
|
||||
#
|
||||
|
||||
def started(self, server, context = None):
|
||||
def started(self, server, context=None):
|
||||
self.sessions[server.id()] = {}
|
||||
|
||||
def stopped(self, server, context = None):
|
||||
|
||||
def stopped(self, server, context=None):
|
||||
self.sessions[server.id()] = {}
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -37,45 +37,41 @@
|
||||
# once they become active again
|
||||
#
|
||||
|
||||
from mumo_module import (commaSeperatedIntegers,
|
||||
commaSeperatedBool,
|
||||
commaSeperatedStrings,
|
||||
MumoModule)
|
||||
|
||||
from threading import Timer
|
||||
import re
|
||||
from threading import Timer
|
||||
|
||||
|
||||
from config import commaSeperatedIntegers, commaSeperatedBool, commaSeperatedStrings
|
||||
from mumo_module import MumoModule
|
||||
|
||||
|
||||
class idlemove(MumoModule):
|
||||
default_config = {'idlemove':(
|
||||
('interval', float, 0.1),
|
||||
('servers', commaSeperatedIntegers, []),
|
||||
),
|
||||
lambda x: re.match('(all)|(server_\d+)', x):(
|
||||
('threshold', commaSeperatedIntegers, [3600]),
|
||||
('mute', commaSeperatedBool, [True]),
|
||||
('deafen', commaSeperatedBool, [False]),
|
||||
('channel', commaSeperatedIntegers, [1]),
|
||||
('source_channel', commaSeperatedIntegers, [-1]),
|
||||
('whitelist', commaSeperatedStrings, []),
|
||||
('channel_whitelist', commaSeperatedIntegers, [])
|
||||
),
|
||||
}
|
||||
|
||||
default_config = {'idlemove': (
|
||||
('interval', float, 0.1),
|
||||
('servers', commaSeperatedIntegers, []),
|
||||
),
|
||||
lambda x: re.match('(all)|(server_\d+)', x): (
|
||||
['threshold', commaSeperatedIntegers, [3600]],
|
||||
('mute', commaSeperatedBool, [True]),
|
||||
('deafen', commaSeperatedBool, [False]),
|
||||
('channel', commaSeperatedIntegers, [1]),
|
||||
('source_channel', commaSeperatedIntegers, [-1]),
|
||||
('whitelist', commaSeperatedStrings, []),
|
||||
('channel_whitelist', commaSeperatedIntegers, [])
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, name, manager, configuration=None):
|
||||
MumoModule.__init__(self, name, manager, configuration)
|
||||
self.murmur = manager.getMurmurModule()
|
||||
self.watchdog = None
|
||||
|
||||
def connected(self):
|
||||
self.affectedusers = {} # {serverid:set(sessionids,...)}
|
||||
self.affectedusers = {} # {serverid:set(sessionids,...)}
|
||||
|
||||
manager = self.manager()
|
||||
log = self.log()
|
||||
log.debug("Register for Meta & Server callbacks")
|
||||
|
||||
|
||||
cfg = self.cfg()
|
||||
servers = cfg.idlemove.servers
|
||||
if not servers:
|
||||
@ -83,11 +79,11 @@ class idlemove(MumoModule):
|
||||
|
||||
manager.subscribeServerCallbacks(self, servers)
|
||||
manager.subscribeMetaCallbacks(self, servers)
|
||||
|
||||
|
||||
if not self.watchdog:
|
||||
self.watchdog = Timer(cfg.idlemove.interval, self.handleIdleMove)
|
||||
self.watchdog.start()
|
||||
|
||||
|
||||
def disconnected(self):
|
||||
self.affectedusers = {}
|
||||
if self.watchdog:
|
||||
@ -98,58 +94,55 @@ class idlemove(MumoModule):
|
||||
cfg = self.cfg()
|
||||
try:
|
||||
meta = self.manager().getMeta()
|
||||
|
||||
|
||||
if not cfg.idlemove.servers:
|
||||
servers = meta.getBootedServers()
|
||||
else:
|
||||
servers = [meta.getServer(server) for server in cfg.idlemove.servers]
|
||||
|
||||
|
||||
for server in servers:
|
||||
if not server: continue
|
||||
|
||||
if server:
|
||||
for user in server.getUsers().itervalues():
|
||||
self.UpdateUserAutoAway(server, user)
|
||||
for user in server.getUsers().values():
|
||||
self.UpdateUserAutoAway(server, user)
|
||||
finally:
|
||||
# Renew the timer
|
||||
self.watchdog = Timer(cfg.idlemove.interval, self.handleIdleMove)
|
||||
self.watchdog.start()
|
||||
|
||||
|
||||
def UpdateUserAutoAway(self, server, user):
|
||||
log = self.log()
|
||||
sid = server.id()
|
||||
|
||||
|
||||
try:
|
||||
scfg = getattr(self.cfg(), 'server_%d' % sid)
|
||||
except AttributeError:
|
||||
scfg = self.cfg().all
|
||||
|
||||
|
||||
try:
|
||||
index = self.affectedusers[sid]
|
||||
except KeyError:
|
||||
self.affectedusers[sid] = set()
|
||||
index = self.affectedusers[sid]
|
||||
|
||||
|
||||
# Check if the user is whitelisted
|
||||
if user.name in scfg.whitelist:
|
||||
return
|
||||
|
||||
# Remember values so we can see changes later
|
||||
threshold = None
|
||||
mute = user.mute
|
||||
deafen = user.deaf
|
||||
channel = user.channel
|
||||
|
||||
|
||||
update = False
|
||||
over_threshold = False
|
||||
|
||||
|
||||
# Search all our stages top down for a violated treshold and pick the first
|
||||
for i in range(len(scfg.threshold) - 1, -1, -1):
|
||||
try:
|
||||
source_channel = scfg.source_channel[i]
|
||||
except IndexError:
|
||||
source_channel = -1
|
||||
|
||||
|
||||
try:
|
||||
threshold = scfg.threshold[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())
|
||||
continue
|
||||
|
||||
if user.idlesecs > threshold and\
|
||||
user.channel not in scfg.channel_whitelist and\
|
||||
(source_channel == -1 or\
|
||||
user.channel == source_channel or\
|
||||
user.channel == channel):
|
||||
|
||||
if user.idlesecs > threshold and user.channel not in scfg.channel_whitelist and (
|
||||
source_channel == -1 or user.channel == source_channel or user.channel == channel):
|
||||
|
||||
over_threshold = True
|
||||
# Update if state changes needed
|
||||
if user.deaf != deafen:
|
||||
update = True
|
||||
if user.mute != mute:
|
||||
update = True
|
||||
if channel >= 0 and user.channel != channel:
|
||||
if 0 <= channel != user.channel:
|
||||
update = True
|
||||
|
||||
|
||||
if update:
|
||||
index.add(user.session)
|
||||
log.info('%ds > %ds: State transition for user %s (%d/%d) from mute %s -> %s / deaf %s -> %s | channel %d -> %d on server %d',
|
||||
user.idlesecs, threshold, user.name, user.session, user.userid, user.mute, mute, user.deaf, deafen,
|
||||
user.channel, channel, server.id())
|
||||
log.info(
|
||||
'%ds > %ds: State transition for user %s (%d/%d) from mute %s -> %s / deaf %s -> %s | channel %d -> %d on server %d',
|
||||
user.idlesecs, threshold, user.name, user.session, user.userid, user.mute, mute, user.deaf,
|
||||
deafen,
|
||||
user.channel, channel, server.id())
|
||||
break
|
||||
|
||||
|
||||
if not over_threshold and user.session in self.affectedusers[sid]:
|
||||
deafen = False
|
||||
mute = False
|
||||
@ -188,15 +180,15 @@ class idlemove(MumoModule):
|
||||
index.remove(user.session)
|
||||
log.info("Restore user %s (%d/%d) on server %d", user.name, user.session, user.userid, server.id())
|
||||
update = True
|
||||
|
||||
|
||||
if update:
|
||||
user.deaf = deafen
|
||||
user.mute = mute
|
||||
user.channel = channel
|
||||
server.setState(user)
|
||||
|
||||
|
||||
#
|
||||
#--- Server callback functions
|
||||
# --- Server callback functions
|
||||
#
|
||||
def userDisconnected(self, server, state, context=None):
|
||||
try:
|
||||
@ -205,27 +197,35 @@ class idlemove(MumoModule):
|
||||
index.remove(state.session)
|
||||
except KeyError:
|
||||
pass
|
||||
|
||||
|
||||
def userStateChanged(self, server, state, context=None):
|
||||
self.UpdateUserAutoAway(server, state)
|
||||
|
||||
def userConnected(self, server, state, context=None): pass # Unused callbacks
|
||||
def userTextMessage(self, server, user, message, current=None): pass
|
||||
def channelCreated(self, server, state, context=None): pass
|
||||
def channelRemoved(self, server, state, context=None): pass
|
||||
def channelStateChanged(self, server, state, context=None): pass
|
||||
|
||||
def userConnected(self, server, state, context=None):
|
||||
pass # Unused callbacks
|
||||
|
||||
def userTextMessage(self, server, user, message, current=None):
|
||||
pass
|
||||
|
||||
def channelCreated(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelRemoved(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelStateChanged(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
#
|
||||
#--- Meta callback functions
|
||||
# --- Meta callback functions
|
||||
#
|
||||
|
||||
def started(self, server, context = None):
|
||||
|
||||
def started(self, server, context=None):
|
||||
sid = server.id()
|
||||
self.affectedusers[sid] = set()
|
||||
self.log().debug('Handling server %d', sid)
|
||||
|
||||
def stopped(self, server, context = None):
|
||||
|
||||
def stopped(self, server, context=None):
|
||||
sid = server.id()
|
||||
self.affectedusers[sid] = set()
|
||||
self.log().debug('Server %d gone', sid)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -35,24 +35,25 @@
|
||||
# they connect regardless of which channel they were in when they left.
|
||||
#
|
||||
|
||||
from mumo_module import (commaSeperatedIntegers,
|
||||
MumoModule)
|
||||
import re
|
||||
|
||||
from config import commaSeperatedIntegers
|
||||
from mumo_module import MumoModule
|
||||
|
||||
|
||||
class onjoin(MumoModule):
|
||||
default_config = {'onjoin':(
|
||||
('servers', commaSeperatedIntegers, []),
|
||||
),
|
||||
'all':(
|
||||
('channel', int, 1),
|
||||
),
|
||||
lambda x: re.match('server_\d+', x):(
|
||||
('channel', int, 1),
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, name, manager, configuration = None):
|
||||
default_config = {'onjoin': (
|
||||
('servers', commaSeperatedIntegers, []),
|
||||
),
|
||||
'all': (
|
||||
('channel', int, 1),
|
||||
),
|
||||
lambda x: re.match('server_\d+', x): (
|
||||
('channel', int, 1),
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, name, manager, configuration=None):
|
||||
MumoModule.__init__(self, name, manager, configuration)
|
||||
self.murmur = manager.getMurmurModule()
|
||||
|
||||
@ -60,39 +61,53 @@ class onjoin(MumoModule):
|
||||
manager = self.manager()
|
||||
log = self.log()
|
||||
log.debug("Register for Server callbacks")
|
||||
|
||||
|
||||
servers = self.cfg().onjoin.servers
|
||||
if not servers:
|
||||
servers = manager.SERVERS_ALL
|
||||
|
||||
|
||||
manager.subscribeServerCallbacks(self, servers)
|
||||
|
||||
def disconnected(self): pass
|
||||
|
||||
|
||||
def disconnected(self):
|
||||
pass
|
||||
|
||||
#
|
||||
#--- Server callback functions
|
||||
# --- Server callback functions
|
||||
#
|
||||
|
||||
def userConnected(self, server, state, context = None):
|
||||
|
||||
def userConnected(self, server, state, context=None):
|
||||
log = self.log()
|
||||
sid = server.id()
|
||||
try:
|
||||
scfg = getattr(self.cfg(), 'server_%d' % sid)
|
||||
except AttributeError:
|
||||
scfg = self.cfg().all
|
||||
|
||||
|
||||
if state.channel != scfg.channel:
|
||||
log.debug("Moving user '%s' from channel %d to %d on server %d", state.name, state.channel, scfg.channel, sid)
|
||||
log.debug("Moving user '%s' from channel %d to %d on server %d", state.name, state.channel, scfg.channel,
|
||||
sid)
|
||||
state.channel = scfg.channel
|
||||
|
||||
|
||||
try:
|
||||
server.setState(state)
|
||||
except self.murmur.InvalidChannelException:
|
||||
log.error("Moving user '%s' failed, target channel %d does not exist on server %d", state.name, scfg.channel, sid)
|
||||
|
||||
def userDisconnected(self, server, state, context = None): pass
|
||||
def userStateChanged(self, server, state, context = None): pass
|
||||
def 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
|
||||
log.error("Moving user '%s' failed, target channel %d does not exist on server %d", state.name,
|
||||
scfg.channel, sid)
|
||||
|
||||
def userDisconnected(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def userStateChanged(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def userTextMessage(self, server, user, message, current=None):
|
||||
pass
|
||||
|
||||
def channelCreated(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelRemoved(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelStateChanged(self, server, state, context=None):
|
||||
pass
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2015 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -35,17 +35,19 @@
|
||||
# entries to a user's context menu.
|
||||
#
|
||||
|
||||
from mumo_module import (commaSeperatedIntegers,
|
||||
MumoModule)
|
||||
import cgi
|
||||
|
||||
from config import commaSeperatedIntegers
|
||||
from mumo_module import MumoModule
|
||||
|
||||
|
||||
class samplecontext(MumoModule):
|
||||
default_config = {'samplecontext':(
|
||||
('servers', commaSeperatedIntegers, []),
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, name, manager, configuration = None):
|
||||
default_config = {'samplecontext': (
|
||||
('servers', commaSeperatedIntegers, []),
|
||||
),
|
||||
}
|
||||
|
||||
def __init__(self, name, manager, configuration=None):
|
||||
MumoModule.__init__(self, name, manager, configuration)
|
||||
self.murmur = manager.getMurmurModule()
|
||||
self.action_poke_user = manager.getUniqueAction()
|
||||
@ -56,19 +58,19 @@ class samplecontext(MumoModule):
|
||||
manager = self.manager()
|
||||
log = self.log()
|
||||
log.debug("Register for Server callbacks")
|
||||
|
||||
|
||||
servers = self.cfg().samplecontext.servers
|
||||
if not servers:
|
||||
servers = manager.SERVERS_ALL
|
||||
|
||||
|
||||
manager.subscribeServerCallbacks(self, servers)
|
||||
|
||||
|
||||
def disconnected(self): pass
|
||||
|
||||
#
|
||||
#--- Server callback functions
|
||||
# --- Server callback functions
|
||||
#
|
||||
|
||||
|
||||
def __on_poke_user(self, server, action, user, target):
|
||||
assert action == self.action_poke_user
|
||||
self.log().info(user.name + " poked " + target.name)
|
||||
@ -78,7 +80,7 @@ class samplecontext(MumoModule):
|
||||
assert action == self.action_info
|
||||
self.log().info(user.name + " wants info on " + str(target));
|
||||
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):
|
||||
# This will remove the entry identified by "action" from
|
||||
@ -86,7 +88,7 @@ class samplecontext(MumoModule):
|
||||
self.log().info(user.name + " triggered removal")
|
||||
self.manager().removeContextMenuEntry(server, action)
|
||||
|
||||
def userConnected(self, server, user, context = None):
|
||||
def userConnected(self, server, user, context=None):
|
||||
# Adding the entries here means if mumo starts up after users
|
||||
# already connected they won't have the new entries before they
|
||||
# reconnect. You can also use the "connected" callback to
|
||||
@ -97,36 +99,40 @@ class samplecontext(MumoModule):
|
||||
|
||||
manager = self.manager()
|
||||
manager.addContextMenuEntry(
|
||||
server, # Server of user
|
||||
user, # User which should receive the new entry
|
||||
self.action_poke_user, # Identifier for the action
|
||||
"Poke", # Text in the client
|
||||
self.__on_poke_user, # Callback called when user uses the entry
|
||||
self.murmur.ContextUser # We only want to show this entry on users
|
||||
server, # Server of user
|
||||
user, # User which should receive the new entry
|
||||
self.action_poke_user, # Identifier for the action
|
||||
"Poke", # Text in the client
|
||||
self.__on_poke_user, # Callback called when user uses the entry
|
||||
self.murmur.ContextUser # We only want to show this entry on users
|
||||
)
|
||||
|
||||
manager.addContextMenuEntry(
|
||||
server,
|
||||
user,
|
||||
self.action_info,
|
||||
"Info",
|
||||
self.__on_info,
|
||||
self.murmur.ContextUser | self.murmur.ContextChannel # Show for users and channels
|
||||
server,
|
||||
user,
|
||||
self.action_info,
|
||||
"Info",
|
||||
self.__on_info,
|
||||
self.murmur.ContextUser | self.murmur.ContextChannel # Show for users and channels
|
||||
)
|
||||
|
||||
manager.addContextMenuEntry(
|
||||
server,
|
||||
user,
|
||||
self.action_remove,
|
||||
"Remove this entry from everyone",
|
||||
self.__on_remove_this,
|
||||
self.murmur.ContextUser | self.murmur.ContextChannel | self.murmur.ContextServer
|
||||
server,
|
||||
user,
|
||||
self.action_remove,
|
||||
"Remove this entry from everyone",
|
||||
self.__on_remove_this,
|
||||
self.murmur.ContextUser | self.murmur.ContextChannel | self.murmur.ContextServer
|
||||
)
|
||||
|
||||
def userDisconnected(self, server, state, context = None): pass
|
||||
def userStateChanged(self, server, state, context = None): pass
|
||||
def userDisconnected(self, server, state, context=None): pass
|
||||
|
||||
def userStateChanged(self, server, state, context=None): pass
|
||||
|
||||
def userTextMessage(self, server, user, message, current=None): pass
|
||||
def channelCreated(self, server, state, context = None): pass
|
||||
def channelRemoved(self, server, state, context = None): pass
|
||||
def channelStateChanged(self, server, state, context = None): pass
|
||||
|
||||
def channelCreated(self, server, state, context=None): pass
|
||||
|
||||
def channelRemoved(self, server, state, context=None): pass
|
||||
|
||||
def channelStateChanged(self, server, state, context=None): pass
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2011 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -34,19 +34,20 @@
|
||||
# This module allows asking the server for the last time it saw a specific player
|
||||
#
|
||||
|
||||
from mumo_module import (commaSeperatedIntegers,
|
||||
MumoModule)
|
||||
|
||||
from datetime import timedelta
|
||||
|
||||
from config import commaSeperatedIntegers
|
||||
from mumo_module import MumoModule
|
||||
|
||||
|
||||
class seen(MumoModule):
|
||||
default_config = {'seen':(
|
||||
('servers', commaSeperatedIntegers, []),
|
||||
('keyword', str, '!seen')
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, name, manager, configuration = None):
|
||||
default_config = {'seen': (
|
||||
('servers', commaSeperatedIntegers, []),
|
||||
('keyword', str, '!seen')
|
||||
)
|
||||
}
|
||||
|
||||
def __init__(self, name, manager, configuration=None):
|
||||
MumoModule.__init__(self, name, manager, configuration)
|
||||
self.murmur = manager.getMurmurModule()
|
||||
self.keyword = self.cfg().seen.keyword
|
||||
@ -55,31 +56,33 @@ class seen(MumoModule):
|
||||
manager = self.manager()
|
||||
log = self.log()
|
||||
log.debug("Register for Server callbacks")
|
||||
|
||||
|
||||
servers = self.cfg().seen.servers
|
||||
if not servers:
|
||||
servers = manager.SERVERS_ALL
|
||||
|
||||
|
||||
manager.subscribeServerCallbacks(self, servers)
|
||||
|
||||
def disconnected(self): pass
|
||||
|
||||
|
||||
def disconnected(self):
|
||||
pass
|
||||
|
||||
def sendMessage(self, server, user, message, msg):
|
||||
if message.channels:
|
||||
server.sendMessageChannel(user.channel, False, msg)
|
||||
else:
|
||||
server.sendMessage(user.session, msg)
|
||||
server.sendMessage(message.sessions[0], msg)
|
||||
|
||||
#
|
||||
#--- Server callback functions
|
||||
# --- Server callback functions
|
||||
#
|
||||
|
||||
|
||||
def userTextMessage(self, server, user, message, current=None):
|
||||
if message.text.startswith(self.keyword) and \
|
||||
(len(message.sessions) == 1 or
|
||||
(len(message.channels) == 1 and \
|
||||
message.channels[0] == user.channel)):
|
||||
|
||||
(len(message.sessions) == 1 or
|
||||
(len(message.channels) == 1 and \
|
||||
message.channels[0] == user.channel)):
|
||||
|
||||
tuname = message.text[len(self.keyword):].strip()
|
||||
self.log().debug("User %s (%d|%d) on server %d asking for '%s'",
|
||||
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
|
||||
self.sendMessage(server, user, message, msg)
|
||||
return
|
||||
|
||||
|
||||
# Check online users
|
||||
for cuser in server.getUsers().itervalues():
|
||||
for cuser in server.getUsers().values():
|
||||
if tuname == cuser.name:
|
||||
msg = "User '%s' is currently online, has been idle for %s" % (tuname,
|
||||
timedelta(seconds=cuser.idlesecs))
|
||||
timedelta(seconds=cuser.idlesecs))
|
||||
self.sendMessage(server, user, message, msg)
|
||||
return
|
||||
|
||||
|
||||
# Check registrations
|
||||
for cuid, cuname in server.getRegisteredUsers(tuname).iteritems():
|
||||
for cuid, cuname in server.getRegisteredUsers(tuname).items():
|
||||
if cuname == tuname:
|
||||
ureg = server.getRegistration(cuid)
|
||||
if ureg:
|
||||
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)
|
||||
return
|
||||
|
||||
|
||||
msg = "I don't know who user '%s' is" % tuname
|
||||
self.sendMessage(server, user, message, msg)
|
||||
|
||||
|
||||
|
||||
def userConnected(self, server, state, context = None): pass
|
||||
def userDisconnected(self, server, state, context = None): pass
|
||||
def userStateChanged(self, server, state, context = None): pass
|
||||
|
||||
def channelCreated(self, server, state, context = None): pass
|
||||
def channelRemoved(self, server, state, context = None): pass
|
||||
def channelStateChanged(self, server, state, context = None): pass
|
||||
def userConnected(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def userDisconnected(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def userStateChanged(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelCreated(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelRemoved(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
def channelStateChanged(self, server, state, context=None):
|
||||
pass
|
||||
|
@ -1 +1 @@
|
||||
from source import source
|
||||
from .source import source
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -31,13 +31,14 @@
|
||||
|
||||
import sqlite3
|
||||
|
||||
#TODO: Functions returning channels probably should return a dict instead of a tuple
|
||||
|
||||
# TODO: Functions returning channels probably should return a dict instead of a tuple
|
||||
|
||||
class SourceDB(object):
|
||||
NO_SERVER = ""
|
||||
NO_TEAM = -1
|
||||
|
||||
def __init__(self, path = ":memory:"):
|
||||
def __init__(self, path=":memory:"):
|
||||
"""
|
||||
Initialize the sqlite database in the given path. If no path
|
||||
is given the database is created in memory.
|
||||
@ -54,7 +55,7 @@ class SourceDB(object):
|
||||
UNIQUE(sid, cid),
|
||||
PRIMARY KEY (sid, game, server, team)
|
||||
)""")
|
||||
|
||||
|
||||
self.db.execute("""
|
||||
CREATE TABLE IF NOT EXISTS mapped_names (
|
||||
sid INTEGER NOT NULL,
|
||||
@ -75,38 +76,39 @@ class SourceDB(object):
|
||||
self.db.commit()
|
||||
self.db.close()
|
||||
self.db = None
|
||||
|
||||
|
||||
def isOk(self):
|
||||
"""
|
||||
True if the database is correctly initialized
|
||||
"""
|
||||
return self.db != None
|
||||
|
||||
|
||||
def nameFor(self, sid, game, server = NO_SERVER, team = NO_TEAM, default = ""):
|
||||
return self.db is not None
|
||||
|
||||
def nameFor(self, sid, game, server=NO_SERVER, team=NO_TEAM, default=""):
|
||||
"""
|
||||
Returns the mapped name for the given parameters or default if no
|
||||
mapping exists.
|
||||
"""
|
||||
assert(sid != None and game != None)
|
||||
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
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()
|
||||
assert (sid is not None and game is not None)
|
||||
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
v = self.db.execute("SELECT name FROM mapped_names WHERE sid is ? and game is ? and server is ? and team is ?",
|
||||
[sid, game, server, team]).fetchone()
|
||||
return v[0] if v else default
|
||||
|
||||
def mapName(self, name, sid, game, server = NO_SERVER, team = NO_TEAM):
|
||||
|
||||
def mapName(self, name, sid, game, server=NO_SERVER, team=NO_TEAM):
|
||||
"""
|
||||
Stores a mapping for the given (sid, game, server, team) combination
|
||||
to the given name. The mapping can then be retrieved with nameFor() in
|
||||
the future.
|
||||
"""
|
||||
assert(sid != None and game != None)
|
||||
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
self.db.execute("INSERT OR REPLACE into mapped_names (sid, game, server, team, name) VALUES (?,?,?,?,?)",[sid, game, server, team, name])
|
||||
assert (sid is not None and game is not None)
|
||||
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
self.db.execute("INSERT OR REPLACE into mapped_names (sid, game, server, team, name) VALUES (?,?,?,?,?)",
|
||||
[sid, game, server, team, name])
|
||||
self.db.commit()
|
||||
|
||||
def cidFor(self, sid, game, server = NO_SERVER, team = NO_TEAM):
|
||||
|
||||
def cidFor(self, sid, game, server=NO_SERVER, team=NO_TEAM):
|
||||
"""
|
||||
Returns the channel id for game specific channel. If only game
|
||||
is passed the game root channel cid is returned. If additionally
|
||||
@ -115,10 +117,12 @@ class SourceDB(object):
|
||||
If no channel matching the arguments has been registered with the database
|
||||
before None is returned.
|
||||
"""
|
||||
assert(sid != None and game != None)
|
||||
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
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()
|
||||
assert (sid is not None and game is not None)
|
||||
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
v = self.db.execute(
|
||||
"SELECT cid FROM controlled_channels WHERE sid is ? and game is ? and server is ? and team is ?",
|
||||
[sid, game, server, team]).fetchone()
|
||||
return v[0] if v else None
|
||||
|
||||
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 None if the cid is unknown.
|
||||
"""
|
||||
assert(sid != None and cid != None)
|
||||
return self.db.execute("SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and cid is ?", [sid, cid]).fetchone()
|
||||
|
||||
def channelFor(self, sid, game, server = NO_SERVER, team = NO_TEAM):
|
||||
assert (sid is not None and cid is not None)
|
||||
return self.db.execute(
|
||||
"SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and cid is ?",
|
||||
[sid, cid]).fetchone()
|
||||
|
||||
def channelFor(self, sid, game, server=NO_SERVER, team=NO_TEAM):
|
||||
"""
|
||||
Returns matching channel as (sid, cid, game, server, team) tuple. Matching
|
||||
behavior is the same as for cidFor()
|
||||
"""
|
||||
assert(sid != None and game != None)
|
||||
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
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()
|
||||
assert (sid is not None and game is not None)
|
||||
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
v = self.db.execute(
|
||||
"SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and game is ? and server is ? and team is ?",
|
||||
[sid, game, server, team]).fetchone()
|
||||
return v
|
||||
|
||||
def channelsFor(self, sid, game, server = NO_SERVER, team = NO_TEAM):
|
||||
|
||||
def channelsFor(self, sid, game, server=NO_SERVER, team=NO_TEAM):
|
||||
"""
|
||||
Returns matching channels as a list of (sid, cid, game, server, team) tuples.
|
||||
If only the game is passed all server and team channels are matched.
|
||||
This can be limited by passing server (and team).
|
||||
Returns empty list if no matches are found.
|
||||
"""
|
||||
assert(sid != None and game != None)
|
||||
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
assert (sid is not None and game is not None)
|
||||
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
suffix, params = self.__whereClauseForOptionals(server, team)
|
||||
return self.db.execute("SELECT sid, cid, game, server, team FROM controlled_channels WHERE sid is ? and game is ?" + suffix, [sid, game] + params).fetchall()
|
||||
|
||||
def registerChannel(self, sid, cid, game, server = NO_SERVER, team = NO_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()
|
||||
|
||||
def registerChannel(self, sid, cid, game, server=NO_SERVER, team=NO_TEAM):
|
||||
"""
|
||||
Register a given channel with the database.
|
||||
"""
|
||||
assert(sid != None and game != None)
|
||||
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
self.db.execute("INSERT INTO controlled_channels (sid, cid, game, server, team) VALUES (?,?,?,?,?)", [sid, cid, game, server, team])
|
||||
assert (sid is not None and game is not None)
|
||||
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
self.db.execute("INSERT INTO controlled_channels (sid, cid, game, server, team) VALUES (?,?,?,?,?)",
|
||||
[sid, cid, game, server, team])
|
||||
self.db.commit()
|
||||
return True
|
||||
|
||||
|
||||
def __whereClauseForOptionals(self, server, team):
|
||||
"""
|
||||
Generates where class conditions that interpret missing server
|
||||
@ -171,49 +182,49 @@ class SourceDB(object):
|
||||
|
||||
Returns (suffix, additional parameters) tuple
|
||||
"""
|
||||
|
||||
|
||||
if server != self.NO_SERVER and team != self.NO_TEAM:
|
||||
return (" and server is ? and team is ?", [server, team])
|
||||
return " and server is ? and team is ?", [server, team]
|
||||
elif server != self.NO_SERVER:
|
||||
return (" and server is ?", [server])
|
||||
return " and server is ?", [server]
|
||||
else:
|
||||
return ("", [])
|
||||
|
||||
def unregisterChannel(self, sid, game, server = NO_SERVER, team = NO_TEAM):
|
||||
return "", []
|
||||
|
||||
def unregisterChannel(self, sid, game, server=NO_SERVER, team=NO_TEAM):
|
||||
"""
|
||||
Unregister a channel previously registered with the database.
|
||||
"""
|
||||
assert(sid != None and game != None)
|
||||
assert(not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
assert (sid is not None and game is not None)
|
||||
assert (not (team != self.NO_TEAM and server == self.NO_SERVER))
|
||||
|
||||
suffix, params = self.__whereClauseForOptionals(server, team)
|
||||
self.db.execute("DELETE FROM controlled_channels WHERE sid is ? and game is ?" + suffix, [sid, game] + params)
|
||||
self.db.commit()
|
||||
|
||||
|
||||
def dropChannel(self, 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.commit()
|
||||
|
||||
|
||||
def isRegisteredChannel(self, sid, cid):
|
||||
"""
|
||||
Returns true if a channel with given sid and cid is registered
|
||||
"""
|
||||
assert(sid != None and cid != None)
|
||||
|
||||
assert (sid is not None and cid is not None)
|
||||
|
||||
res = self.db.execute("SELECT cid FROM controlled_channels WHERE sid is ? and cid is ?", [sid, cid]).fetchone()
|
||||
return res != None
|
||||
|
||||
return res is not None
|
||||
|
||||
def registeredChannels(self):
|
||||
"""
|
||||
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()
|
||||
|
||||
|
||||
def reset(self):
|
||||
"""
|
||||
Deletes everything in the database
|
||||
@ -221,6 +232,7 @@ class SourceDB(object):
|
||||
self.db.execute("DELETE FROM mapped_names")
|
||||
self.db.execute("DELETE FROM controlled_channels")
|
||||
self.db.commit()
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
pass
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,9 +29,11 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import unittest
|
||||
from db import SourceDB
|
||||
import sqlite3
|
||||
import unittest
|
||||
|
||||
from .db import SourceDB
|
||||
|
||||
|
||||
class SourceDBTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
@ -40,227 +42,261 @@ class SourceDBTest(unittest.TestCase):
|
||||
def tearDown(self):
|
||||
self.db.close()
|
||||
|
||||
|
||||
def testOk(self):
|
||||
self.db.reset()
|
||||
|
||||
|
||||
self.assertTrue(self.db.isOk())
|
||||
|
||||
|
||||
def testSingleChannel(self):
|
||||
self.db.reset()
|
||||
|
||||
sid = 5; cid = 10; game = "tf2"; server = "abc[]def"; team = "1"
|
||||
|
||||
sid = 5
|
||||
cid = 10
|
||||
game = "tf2"
|
||||
server = "abc[]def"
|
||||
team = "1"
|
||||
self.assertTrue(self.db.registerChannel(sid, cid, game, server, team))
|
||||
self.assertEqual(self.db.cidFor(sid, game, server, team), cid)
|
||||
self.db.unregisterChannel(sid, game, server, team)
|
||||
self.assertEqual(self.db.cidFor(sid, game, server, team), None)
|
||||
|
||||
|
||||
def testChannelTree(self):
|
||||
self.db.reset()
|
||||
|
||||
sid = 5; game = "tf2"; server = "abc[]def"; team = 0
|
||||
bcid = 10; scid = 11; tcid = 12
|
||||
|
||||
|
||||
sid = 5
|
||||
game = "tf2"
|
||||
server = "abc[]def"
|
||||
team = 0
|
||||
bcid = 10
|
||||
scid = 11
|
||||
tcid = 12
|
||||
|
||||
self.assertTrue(self.db.registerChannel(sid, 1, "canary", server, team))
|
||||
|
||||
|
||||
# Delete whole tree
|
||||
|
||||
|
||||
self.assertTrue(self.db.registerChannel(sid, bcid, game))
|
||||
self.assertTrue(self.db.registerChannel(sid, scid, game, server))
|
||||
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, server), scid)
|
||||
self.assertEqual(self.db.cidFor(sid, game, server, team), tcid)
|
||||
self.assertEqual(self.db.cidFor(sid+1, game, server, team), None)
|
||||
|
||||
self.assertEqual(self.db.cidFor(sid + 1, game, server, team), None)
|
||||
|
||||
self.db.unregisterChannel(sid, game)
|
||||
|
||||
|
||||
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), None)
|
||||
|
||||
# Delete server channel
|
||||
|
||||
|
||||
self.assertTrue(self.db.registerChannel(sid, bcid, game))
|
||||
self.assertTrue(self.db.registerChannel(sid, scid, game, server))
|
||||
self.assertTrue(self.db.registerChannel(sid, tcid, game, server, team))
|
||||
|
||||
|
||||
self.db.unregisterChannel(sid, game, server)
|
||||
|
||||
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, team), None)
|
||||
|
||||
|
||||
self.db.unregisterChannel(sid, game)
|
||||
|
||||
|
||||
# Delete team channel
|
||||
|
||||
self.assertTrue(self.db.registerChannel(sid, bcid, game))
|
||||
self.assertTrue(self.db.registerChannel(sid, scid, game, server))
|
||||
self.assertTrue(self.db.registerChannel(sid, tcid, 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, server), scid)
|
||||
self.assertEqual(self.db.cidFor(sid, game, server, team), None)
|
||||
|
||||
|
||||
self.db.unregisterChannel(sid, game)
|
||||
|
||||
|
||||
# Check canary
|
||||
self.assertEqual(self.db.cidFor(sid, "canary", server, team), 1)
|
||||
self.db.unregisterChannel(sid, "canary", server, team)
|
||||
|
||||
|
||||
def testDropChannel(self):
|
||||
self.db.reset()
|
||||
|
||||
sid = 1; cid = 5; game = "tf"
|
||||
|
||||
sid = 1
|
||||
cid = 5
|
||||
game = "tf"
|
||||
self.db.registerChannel(sid, cid, game)
|
||||
self.db.dropChannel(sid + 1, cid)
|
||||
self.assertEqual(self.db.cidFor(sid, game), cid)
|
||||
self.db.dropChannel(sid, cid)
|
||||
self.assertEqual(self.db.cidFor(sid, game), None)
|
||||
|
||||
|
||||
def testRegisteredChannels(self):
|
||||
self.db.reset()
|
||||
|
||||
sid = 5; game = "tf2"; server = "abc[]def"; team = 1
|
||||
bcid = 10; scid = 11; tcid = 12;
|
||||
|
||||
|
||||
sid = 5
|
||||
game = "tf2"
|
||||
server = "abc[]def"
|
||||
team = 1
|
||||
bcid = 10
|
||||
scid = 11
|
||||
tcid = 12
|
||||
|
||||
self.db.registerChannel(sid, bcid, game)
|
||||
self.db.registerChannel(sid, scid, game, server)
|
||||
self.db.registerChannel(sid+1, tcid, game, server, team)
|
||||
self.db.registerChannel(sid + 1, tcid, game, server, team)
|
||||
self.db.registerChannel(sid, tcid, game, server, team)
|
||||
|
||||
|
||||
expected = [(sid, bcid, game, self.db.NO_SERVER, self.db.NO_TEAM),
|
||||
(sid, scid, game, server, self.db.NO_TEAM),
|
||||
(sid, tcid, game, server, team),
|
||||
(sid+1, tcid, game, server, team)]
|
||||
|
||||
(sid + 1, tcid, game, server, team)]
|
||||
|
||||
self.assertEqual(self.db.registeredChannels(), expected)
|
||||
|
||||
|
||||
def testIsRegisteredChannel(self):
|
||||
self.db.reset()
|
||||
sid = 1; cid = 0; game = "tf"
|
||||
sid = 1
|
||||
cid = 0
|
||||
game = "tf"
|
||||
self.db.registerChannel(sid, cid, game)
|
||||
|
||||
|
||||
self.assertTrue(self.db.isRegisteredChannel(sid, cid))
|
||||
self.assertFalse(self.db.isRegisteredChannel(sid+1, cid))
|
||||
self.assertFalse(self.db.isRegisteredChannel(sid, cid+1))
|
||||
|
||||
self.assertFalse(self.db.isRegisteredChannel(sid + 1, cid))
|
||||
self.assertFalse(self.db.isRegisteredChannel(sid, cid + 1))
|
||||
|
||||
self.db.unregisterChannel(sid, game)
|
||||
|
||||
|
||||
self.assertFalse(self.db.isRegisteredChannel(sid, cid))
|
||||
|
||||
|
||||
def testChannelFor(self):
|
||||
self.db.reset()
|
||||
sid = 1; cid = 0; game = "tf"; server = "serv"; team = 0
|
||||
sid = 1
|
||||
cid = 0
|
||||
game = "tf"
|
||||
server = "serv"
|
||||
team = 0
|
||||
self.db.registerChannel(sid, cid, game)
|
||||
self.db.registerChannel(sid, cid+1, game, server)
|
||||
self.db.registerChannel(sid, cid+2, game, server, team)
|
||||
|
||||
self.db.registerChannel(sid, cid + 1, game, server)
|
||||
self.db.registerChannel(sid, cid + 2, game, server, team)
|
||||
|
||||
res = self.db.channelFor(sid, game, server, team)
|
||||
self.assertEqual(res, (sid, cid + 2, game, server, team))
|
||||
|
||||
|
||||
res = self.db.channelFor(sid, game, server)
|
||||
self.assertEqual(res, (sid, cid + 1, game, server, self.db.NO_TEAM))
|
||||
|
||||
|
||||
res = self.db.channelFor(sid, game)
|
||||
self.assertEqual(res, (sid, cid, game, self.db.NO_SERVER, self.db.NO_TEAM))
|
||||
|
||||
res = self.db.channelFor(sid, game, server, team+5)
|
||||
|
||||
res = self.db.channelFor(sid, game, server, team + 5)
|
||||
self.assertEqual(res, None)
|
||||
|
||||
|
||||
def testChannelForCid(self):
|
||||
self.db.reset()
|
||||
sid = 1; cid = 0; game = "tf"; server = "serv"; team = 0
|
||||
sid = 1
|
||||
cid = 0
|
||||
game = "tf"
|
||||
server = "serv"
|
||||
team = 0
|
||||
self.db.registerChannel(sid, cid, game)
|
||||
self.db.registerChannel(sid, cid+1, game, server)
|
||||
self.db.registerChannel(sid, cid+2, game, server, team)
|
||||
|
||||
self.db.registerChannel(sid, cid + 1, game, server)
|
||||
self.db.registerChannel(sid, cid + 2, game, server, team)
|
||||
|
||||
res = self.db.channelForCid(sid, cid)
|
||||
self.assertEqual(res, (sid, cid, game, self.db.NO_SERVER, self.db.NO_TEAM))
|
||||
|
||||
|
||||
|
||||
res = self.db.channelForCid(sid, cid + 1)
|
||||
self.assertEqual(res, (sid, cid + 1, game, server, self.db.NO_TEAM))
|
||||
|
||||
|
||||
|
||||
res = self.db.channelForCid(sid, cid + 2)
|
||||
self.assertEqual(res, (sid, cid + 2, game, server, team))
|
||||
|
||||
|
||||
|
||||
res = self.db.channelForCid(sid, cid + 3)
|
||||
self.assertEqual(res, None)
|
||||
|
||||
|
||||
def testChannelsFor(self):
|
||||
self.db.reset()
|
||||
sid = 1; cid = 0; game = "tf"; server = "serv"; team = 0
|
||||
sid = 1
|
||||
cid = 0
|
||||
game = "tf"
|
||||
server = "serv"
|
||||
team = 0
|
||||
self.db.registerChannel(sid, cid, game)
|
||||
self.db.registerChannel(sid, cid+1, game, server)
|
||||
self.db.registerChannel(sid, cid+2, game, server, team)
|
||||
|
||||
chans = ((sid, cid+2, game, server, team),
|
||||
(sid, cid+1, game, server, self.db.NO_TEAM),
|
||||
self.db.registerChannel(sid, cid + 1, game, server)
|
||||
self.db.registerChannel(sid, cid + 2, game, server, team)
|
||||
|
||||
chans = ((sid, cid + 2, game, server, team),
|
||||
(sid, cid + 1, game, server, self.db.NO_TEAM),
|
||||
(sid, cid, game, self.db.NO_SERVER, self.db.NO_TEAM))
|
||||
|
||||
|
||||
res = self.db.channelsFor(sid, game, server, team)
|
||||
self.assertItemsEqual(res, chans[0:1])
|
||||
|
||||
self.assertCountEqual(res, chans[0:1])
|
||||
|
||||
res = self.db.channelsFor(sid, game, server)
|
||||
self.assertItemsEqual(res, chans[0:2])
|
||||
|
||||
self.assertCountEqual(res, chans[0:2])
|
||||
|
||||
res = self.db.channelsFor(sid, game)
|
||||
self.assertItemsEqual(res, chans)
|
||||
|
||||
res = self.db.channelsFor(sid+1, game)
|
||||
self.assertItemsEqual(res, [])
|
||||
|
||||
self.assertCountEqual(res, chans)
|
||||
|
||||
res = self.db.channelsFor(sid + 1, game)
|
||||
self.assertCountEqual(res, [])
|
||||
|
||||
def testChannelTableConstraints(self):
|
||||
self.db.reset()
|
||||
|
||||
|
||||
# cid constraint
|
||||
sid = 1; cid = 0; game = "tf"; server = "serv"; team = 0
|
||||
sid = 1
|
||||
cid = 0
|
||||
game = "tf"
|
||||
server = "serv"
|
||||
team = 0
|
||||
self.db.registerChannel(sid, cid, game)
|
||||
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid, "cstrike")
|
||||
|
||||
# combination constraint
|
||||
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid+1000, game)
|
||||
|
||||
self.db.registerChannel(sid, cid+1, game, server)
|
||||
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid+100, game, server)
|
||||
|
||||
self.db.registerChannel(sid, cid+2, game, server, team)
|
||||
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid+200, game, server, team)
|
||||
|
||||
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid + 1000, game)
|
||||
|
||||
self.db.registerChannel(sid, cid + 1, game, server)
|
||||
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid + 100, game, server)
|
||||
|
||||
self.db.registerChannel(sid, cid + 2, game, server, team)
|
||||
self.assertRaises(sqlite3.IntegrityError, self.db.registerChannel, sid, cid + 200, game, server, team)
|
||||
|
||||
def testChannelNameMappingTableConstraints(self):
|
||||
self.db.reset()
|
||||
|
||||
sid = 1; game = "tf"
|
||||
|
||||
|
||||
sid = 1
|
||||
game = "tf"
|
||||
|
||||
# mapName performs an INSERT OR REPLACE which relies on the UNIQUE constraint
|
||||
self.db.mapName("SomeTestName", sid, game)
|
||||
self.db.mapName("SomeOtherName", sid, game)
|
||||
self.assertEqual(self.db.nameFor(sid, game), "SomeOtherName")
|
||||
|
||||
|
||||
def testNameMapping(self):
|
||||
self.db.reset()
|
||||
|
||||
sid = 1; game = "tf"; server = "[12313]";team = 2
|
||||
self.assertEqual(self.db.nameFor(sid, game, default = "test"), "test")
|
||||
|
||||
|
||||
sid = 1
|
||||
game = "tf"
|
||||
server = "[12313]"
|
||||
team = 2
|
||||
self.assertEqual(self.db.nameFor(sid, game, default="test"), "test")
|
||||
|
||||
self.db.mapName("Game", sid, game)
|
||||
self.db.mapName("Game Server", sid, game, server)
|
||||
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, "cstrike", server, team)
|
||||
|
||||
|
||||
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, team), "Game Server Team")
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
# import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -35,16 +35,15 @@
|
||||
# gamestate reported by Mumble positional audio plugins
|
||||
#
|
||||
|
||||
from mumo_module import (MumoModule,
|
||||
commaSeperatedIntegers,
|
||||
commaSeperatedStrings,
|
||||
x2bool)
|
||||
|
||||
from db import SourceDB
|
||||
from users import (User, UserRegistry)
|
||||
|
||||
import re
|
||||
|
||||
|
||||
from config import commaSeperatedStrings, x2bool, commaSeperatedIntegers
|
||||
from mumo_module import MumoModule
|
||||
from .db import SourceDB
|
||||
from .users import (User, UserRegistry)
|
||||
|
||||
|
||||
# noinspection PyPep8Naming
|
||||
class source(MumoModule):
|
||||
"""
|
||||
This class combines the basic mumble moderator callbacks with
|
||||
@ -52,30 +51,30 @@ class source(MumoModule):
|
||||
context and identity information.
|
||||
"""
|
||||
default_game_config = (
|
||||
('name', str, "%(game)s"),
|
||||
('servername', str, "%(server)s"),
|
||||
('teams', commaSeperatedStrings, ["Lobby", "Spectator", "Team one", "Team two", "Team three", "Team four"]),
|
||||
('restrict', x2bool, True),
|
||||
('serverregex', re.compile, re.compile("^\[[\w\d\-\(\):]{1,20}\]$")),
|
||||
('deleteifunused', x2bool, True)
|
||||
)
|
||||
|
||||
default_config = {'source':(
|
||||
('database', str, "source.sqlite"),
|
||||
('basechannelid', int, 0),
|
||||
('mumbleservers', commaSeperatedIntegers, []),
|
||||
('gameregex', re.compile, re.compile("^(tf|dod|cstrike|hl2mp)$")),
|
||||
('groupprefix', str, "source_")
|
||||
),
|
||||
|
||||
# The generic section defines default values which can be overridden in
|
||||
# optional game specific "game:<gameshorthand>" sections
|
||||
('name', str, "%(game)s"),
|
||||
('servername', str, "%(server)s"),
|
||||
('teams', commaSeperatedStrings, ["Lobby", "Spectator", "Team one", "Team two", "Team three", "Team four"]),
|
||||
('restrict', x2bool, True),
|
||||
('serverregex', re.compile, re.compile("^\[[\w\d\-\(\):]{1,20}\]$")),
|
||||
('deleteifunused', x2bool, True)
|
||||
)
|
||||
|
||||
default_config = {'source': (
|
||||
('database', str, "source.sqlite"),
|
||||
('basechannelid', int, 0),
|
||||
('mumbleservers', commaSeperatedIntegers, []),
|
||||
('gameregex', re.compile, re.compile("^(tf|dod|cstrike|hl2mp)$")),
|
||||
('groupprefix', str, "source_")
|
||||
),
|
||||
|
||||
# The generic section defines default values which can be overridden in
|
||||
# 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):
|
||||
MumoModule.__init__(self, name, manager, configuration)
|
||||
self.murmur = manager.getMurmurModule()
|
||||
@ -84,11 +83,11 @@ class source(MumoModule):
|
||||
MumoModule.onStart(self)
|
||||
cfg = self.cfg()
|
||||
self.db = SourceDB(cfg.source.database)
|
||||
|
||||
|
||||
def onStop(self):
|
||||
MumoModule.onStop(self)
|
||||
self.db.close()
|
||||
|
||||
|
||||
def connected(self):
|
||||
"""
|
||||
Makes sure the the plugin is correctly configured once the connection
|
||||
@ -98,21 +97,20 @@ class source(MumoModule):
|
||||
manager = self.manager()
|
||||
log = self.log()
|
||||
log.debug("Register for Server callbacks")
|
||||
|
||||
|
||||
self.meta = manager.getMeta()
|
||||
|
||||
|
||||
servers = set(cfg.source.mumbleservers)
|
||||
if not servers:
|
||||
servers = manager.SERVERS_ALL
|
||||
|
||||
|
||||
self.users = UserRegistry()
|
||||
|
||||
|
||||
self.validateChannelDB()
|
||||
|
||||
|
||||
manager.subscribeServerCallbacks(self, servers)
|
||||
manager.subscribeMetaCallbacks(self, servers)
|
||||
|
||||
|
||||
|
||||
def validateChannelDB(self):
|
||||
"""
|
||||
Makes sure the plugins internal datatbase
|
||||
@ -120,32 +118,33 @@ class source(MumoModule):
|
||||
"""
|
||||
log = self.log()
|
||||
log.debug("Validating channel database")
|
||||
|
||||
|
||||
current_sid = -1
|
||||
current_mumble_server = None
|
||||
|
||||
|
||||
for sid, cid, game, server, team in self.db.registeredChannels():
|
||||
if current_sid != sid:
|
||||
current_mumble_server = self.meta.getServer(sid)
|
||||
current_sid = sid
|
||||
|
||||
|
||||
try:
|
||||
state = current_mumble_server.getChannelState(cid)
|
||||
self.db.mapName(state.name, sid, game, server, team)
|
||||
#TODO: Verify ACL?
|
||||
|
||||
# TODO: Verify ACL?
|
||||
|
||||
except self.murmur.InvalidChannelException:
|
||||
# Channel no longer exists
|
||||
log.debug("(%d) Channel %d no longer exists. Dropped.", sid, cid)
|
||||
self.db.dropChannel(sid, cid)
|
||||
except AttributeError:
|
||||
# Server no longer exists
|
||||
assert(current_mumble_server == None)
|
||||
assert (current_mumble_server is None)
|
||||
log.debug("(%d) Server for channel %d no longer exists. Dropped.", sid, cid)
|
||||
self.db.dropChannel(sid, cid)
|
||||
|
||||
def disconnected(self): pass
|
||||
|
||||
|
||||
def disconnected(self):
|
||||
pass
|
||||
|
||||
def removeFromGroups(self, mumble_server, session, game, server, team):
|
||||
"""
|
||||
Removes the client from all relevant groups
|
||||
@ -153,16 +152,16 @@ class source(MumoModule):
|
||||
sid = mumble_server.id()
|
||||
prefix = self.cfg().source.groupprefix
|
||||
game_cid = self.db.cidFor(sid, game)
|
||||
|
||||
|
||||
group = prefix + game
|
||||
mumble_server.removeUserFromGroup(game_cid, session, group) # Game
|
||||
|
||||
mumble_server.removeUserFromGroup(game_cid, session, group) # Game
|
||||
|
||||
group += "_" + server
|
||||
mumble_server.removeUserFromGroup(game_cid, session, group) # Server
|
||||
|
||||
mumble_server.removeUserFromGroup(game_cid, session, group) # Server
|
||||
|
||||
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):
|
||||
"""
|
||||
Adds the client to all relevant groups
|
||||
@ -170,49 +169,48 @@ class source(MumoModule):
|
||||
sid = mumble_server.id()
|
||||
prefix = self.cfg().source.groupprefix
|
||||
game_cid = self.db.cidFor(sid, game)
|
||||
assert(game_cid != None)
|
||||
|
||||
assert (game_cid is not None)
|
||||
|
||||
group = prefix + game
|
||||
mumble_server.addUserToGroup(game_cid, session, group) # Game
|
||||
|
||||
mumble_server.addUserToGroup(game_cid, session, group) # Game
|
||||
|
||||
group += "_" + server
|
||||
mumble_server.addUserToGroup(game_cid, session, group) # Server
|
||||
|
||||
mumble_server.addUserToGroup(game_cid, session, group) # Server
|
||||
|
||||
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):
|
||||
"""
|
||||
Transitions a user that has been and is currently playing
|
||||
"""
|
||||
assert(new)
|
||||
|
||||
assert new
|
||||
|
||||
target_cid = self.getOrCreateTargetChannelFor(mumble_server, new)
|
||||
|
||||
|
||||
if user_new:
|
||||
self.dlog(sid, new.state, "User started playing: g/s/t %s/%s/%d", new.game, new.server, new.identity["team"])
|
||||
self.dlog(sid, new.state, "User started playing: g/s/t %s/%s/%d", new.game, new.server,
|
||||
new.identity["team"])
|
||||
self.addToGroups(mumble_server, new.state.session, new.game, new.server, new.identity["team"])
|
||||
else:
|
||||
assert old
|
||||
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.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):
|
||||
"""
|
||||
Transitions a user that played but is no longer doing so now.
|
||||
"""
|
||||
assert(old)
|
||||
|
||||
assert old
|
||||
|
||||
self.users.remove(sid, old.state.session)
|
||||
|
||||
|
||||
if new:
|
||||
self.removeFromGroups(mumble_server, old.state.session, old.game, old.server, old.identity["team"])
|
||||
|
||||
|
||||
bcid = self.cfg().source.basechannelid
|
||||
self.dlog(sid, old.state, "User stopped playing. Moving to %d.", bcid)
|
||||
self.moveUserToCid(mumble_server, new.state, bcid)
|
||||
@ -220,7 +218,6 @@ class source(MumoModule):
|
||||
self.dlog(sid, old.state, "User gone")
|
||||
return True
|
||||
|
||||
|
||||
def userLeftChannel(self, mumble_server, old, sid):
|
||||
"""
|
||||
User left channel. Make sure we check for vacancy it if the game it
|
||||
@ -247,37 +244,36 @@ class source(MumoModule):
|
||||
"""
|
||||
sid = mumble_server.id()
|
||||
|
||||
assert(not old or old.valid())
|
||||
|
||||
relevant = old or (new and new.valid())
|
||||
assert (not old or old.valid())
|
||||
|
||||
relevant = old or (new and new.valid())
|
||||
if not relevant:
|
||||
return
|
||||
|
||||
|
||||
user_new = not old and new and new.valid()
|
||||
user_gone = old and (not new or not new.valid())
|
||||
|
||||
|
||||
if not user_gone:
|
||||
moved = self.transitionPresentUser(mumble_server, old, new, sid, user_new)
|
||||
|
||||
|
||||
else:
|
||||
moved = self.transitionGoneUser(mumble_server, old, new, sid)
|
||||
|
||||
|
||||
|
||||
if moved and old:
|
||||
self.userLeftChannel(mumble_server, old, sid)
|
||||
|
||||
|
||||
def getGameName(self, game):
|
||||
"""
|
||||
Returns the unexpanded game specific game name template.
|
||||
"""
|
||||
return self.getGameConfig(game, "name")
|
||||
|
||||
|
||||
def getServerName(self, game):
|
||||
"""
|
||||
Returns the unexpanded game specific server name template.
|
||||
"""
|
||||
return self.getGameConfig(game, "servername")
|
||||
|
||||
|
||||
def getTeamName(self, game, 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]
|
||||
except IndexError:
|
||||
return str(index)
|
||||
|
||||
|
||||
def setACLsForGameChannel(self, mumble_server, game_cid, game):
|
||||
"""
|
||||
Sets the appropriate ACLs for a game channel for the given cid.
|
||||
"""
|
||||
# Shorthands
|
||||
ACL = self.murmur.ACL
|
||||
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
|
||||
W = self.murmur.PermissionWhisper # Whisper
|
||||
S = self.murmur.PermissionSpeak # Speak
|
||||
|
||||
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
|
||||
W = self.murmur.PermissionWhisper # Whisper
|
||||
S = self.murmur.PermissionSpeak # Speak
|
||||
|
||||
groupname = '~' + self.cfg().source.groupprefix + game
|
||||
|
||||
|
||||
mumble_server.setACL(game_cid,
|
||||
[ACL(applyHere = True, # Deny everything
|
||||
applySubs = True,
|
||||
userid = -1,
|
||||
group = 'all',
|
||||
deny = EAT | W | S),
|
||||
ACL(applyHere = True, # Allow enter and traverse to players
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = groupname,
|
||||
allow = EAT)],
|
||||
[], True)
|
||||
|
||||
[ACL(applyHere=True, # Deny everything
|
||||
applySubs=True,
|
||||
userid=-1,
|
||||
group='all',
|
||||
deny=EAT | W | S),
|
||||
ACL(applyHere=True, # Allow enter and traverse to players
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group=groupname,
|
||||
allow=EAT)],
|
||||
[], True)
|
||||
|
||||
def setACLsForServerChannel(self, mumble_server, server_cid, game, server):
|
||||
"""
|
||||
@ -320,20 +315,19 @@ class source(MumoModule):
|
||||
"""
|
||||
# Shorthands
|
||||
ACL = self.murmur.ACL
|
||||
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
|
||||
W = self.murmur.PermissionWhisper # Whisper
|
||||
S = self.murmur.PermissionSpeak # Speak
|
||||
|
||||
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
|
||||
W = self.murmur.PermissionWhisper # Whisper
|
||||
S = self.murmur.PermissionSpeak # Speak
|
||||
|
||||
groupname = '~' + self.cfg().source.groupprefix + game + "_" + server
|
||||
|
||||
|
||||
mumble_server.setACL(server_cid,
|
||||
[ACL(applyHere = True, # Allow enter and traverse to players
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = groupname,
|
||||
allow = EAT)],
|
||||
[], True)
|
||||
|
||||
[ACL(applyHere=True, # Allow enter and traverse to players
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group=groupname,
|
||||
allow=EAT)],
|
||||
[], True)
|
||||
|
||||
def setACLsForTeamChannel(self, mumble_server, team_cid, game, server, team):
|
||||
"""
|
||||
@ -341,19 +335,19 @@ class source(MumoModule):
|
||||
"""
|
||||
# Shorthands
|
||||
ACL = self.murmur.ACL
|
||||
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
|
||||
W = self.murmur.PermissionWhisper # Whisper
|
||||
S = self.murmur.PermissionSpeak # Speak
|
||||
|
||||
EAT = self.murmur.PermissionEnter | self.murmur.PermissionTraverse # Enter And Traverse
|
||||
W = self.murmur.PermissionWhisper # Whisper
|
||||
S = self.murmur.PermissionSpeak # Speak
|
||||
|
||||
groupname = '~' + self.cfg().source.groupprefix + game + "_" + server + "_" + str(team)
|
||||
|
||||
|
||||
mumble_server.setACL(team_cid,
|
||||
[ACL(applyHere = True, # Allow enter and traverse to players
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = groupname,
|
||||
allow = EAT | W | S)],
|
||||
[], True)
|
||||
[ACL(applyHere=True, # Allow enter and traverse to players
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group=groupname,
|
||||
allow=EAT | W | S)],
|
||||
[], True)
|
||||
|
||||
def getOrCreateGameChannelFor(self, mumble_server, game, server, sid, cfg, log, namevars):
|
||||
"""
|
||||
@ -362,23 +356,22 @@ class source(MumoModule):
|
||||
"""
|
||||
sid = mumble_server.id()
|
||||
game_cid = self.db.cidFor(sid, game)
|
||||
if game_cid == None:
|
||||
if game_cid is None:
|
||||
game_channel_name = self.db.nameFor(sid, game,
|
||||
default = (self.getGameName(game) % namevars))
|
||||
|
||||
default=(self.getGameName(game) % namevars))
|
||||
|
||||
log.debug("(%d) Creating game channel '%s' below %d", sid, game_channel_name, cfg.source.basechannelid)
|
||||
game_cid = mumble_server.addChannel(game_channel_name, cfg.source.basechannelid)
|
||||
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)
|
||||
|
||||
|
||||
if self.getGameConfig(game, "restrict"):
|
||||
log.debug("(%d) Setting ACL's for new game channel (cid %d)", sid, game_cid)
|
||||
self.setACLsForGameChannel(mumble_server, game_cid, game)
|
||||
|
||||
|
||||
log.debug("(%d) Game channel created and registered (cid %d)", sid, game_cid)
|
||||
return game_cid
|
||||
|
||||
|
||||
def getOrCreateServerChannelFor(self, mumble_server, game, server, team, sid, log, namevars, game_cid):
|
||||
"""
|
||||
Helper function for getting or creating only the server channel. The game
|
||||
@ -386,43 +379,42 @@ class source(MumoModule):
|
||||
server channel.
|
||||
"""
|
||||
server_cid = self.db.cidFor(sid, game, server)
|
||||
if server_cid == None:
|
||||
if server_cid is None:
|
||||
server_channel_name = self.db.nameFor(sid, game, server,
|
||||
default = self.getServerName(game) % namevars)
|
||||
|
||||
default=self.getServerName(game) % namevars)
|
||||
|
||||
log.debug("(%d) Creating server channel '%s' below %d", sid, server_channel_name, game_cid)
|
||||
server_cid = mumble_server.addChannel(server_channel_name, game_cid)
|
||||
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"):
|
||||
log.debug("(%d) Setting ACL's for new server channel (cid %d)", sid, server_cid)
|
||||
self.setACLsForServerChannel(mumble_server, server_cid, game, server)
|
||||
|
||||
|
||||
log.debug("(%d) Server channel created and registered (cid %d)", sid, server_cid)
|
||||
return server_cid
|
||||
|
||||
|
||||
def getOrCreateTeamChannelFor(self, mumble_server, game, server, team, sid, log, server_cid):
|
||||
"""
|
||||
Helper function for getting or creating only the team channel. Game and
|
||||
server channel must already exist. Returns the cid of the existing or
|
||||
created team channel.
|
||||
"""
|
||||
|
||||
|
||||
team_cid = self.db.cidFor(sid, game, server, team)
|
||||
if team_cid == None:
|
||||
if team_cid is None:
|
||||
team_channel_name = self.db.nameFor(sid, game, server, team,
|
||||
default = self.getTeamName(game, team))
|
||||
|
||||
default=self.getTeamName(game, team))
|
||||
|
||||
log.debug("(%d) Creating team channel '%s' below %d", sid, team_channel_name, server_cid)
|
||||
team_cid = mumble_server.addChannel(team_channel_name, server_cid)
|
||||
self.db.registerChannel(sid, team_cid, game, server, team)
|
||||
|
||||
|
||||
if self.getGameConfig(game, "restrict"):
|
||||
log.debug("(%d) Setting ACL's for new team channel (cid %d)", sid, team_cid)
|
||||
self.setACLsForTeamChannel(mumble_server, team_cid, game, server, team)
|
||||
|
||||
|
||||
log.debug("(%d) Team channel created and registered (cid %d)", sid, team_cid)
|
||||
return team_cid
|
||||
|
||||
@ -435,16 +427,16 @@ class source(MumoModule):
|
||||
sid = mumble_server.id()
|
||||
cfg = self.cfg()
|
||||
log = self.log()
|
||||
|
||||
namevars = {'game' : game,
|
||||
'server' : server}
|
||||
|
||||
|
||||
namevars = {'game': game,
|
||||
'server': server}
|
||||
|
||||
game_cid = self.getOrCreateGameChannelFor(mumble_server, game, server, sid, cfg, log, namevars)
|
||||
server_cid = self.getOrCreateServerChannelFor(mumble_server, game, server, team, sid, log, namevars, game_cid)
|
||||
team_cid = self.getOrCreateTeamChannelFor(mumble_server, game, server, team, sid, log, server_cid)
|
||||
|
||||
|
||||
return team_cid
|
||||
|
||||
|
||||
def moveUserToCid(self, server, state, cid):
|
||||
"""
|
||||
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)
|
||||
state.channel = cid
|
||||
server.setState(state)
|
||||
|
||||
|
||||
def getOrCreateTargetChannelFor(self, mumble_server, user):
|
||||
"""
|
||||
Returns the cid of the target channel for this user. If needed
|
||||
@ -462,8 +454,8 @@ class source(MumoModule):
|
||||
user.game,
|
||||
user.server,
|
||||
user.identity["team"])
|
||||
|
||||
def moveUser(self, mumble_server, user, target_cid = None):
|
||||
|
||||
def moveUser(self, mumble_server, user, target_cid=None):
|
||||
"""
|
||||
Move user according to current game state.
|
||||
|
||||
@ -477,21 +469,21 @@ class source(MumoModule):
|
||||
server = user.server
|
||||
team = user.identity["team"]
|
||||
sid = mumble_server.id()
|
||||
|
||||
|
||||
source_cid = state.channel
|
||||
|
||||
if target_cid == None:
|
||||
|
||||
if target_cid is None:
|
||||
target_cid = self.getOrCreateChannelFor(mumble_server, game, server, team)
|
||||
|
||||
|
||||
if source_cid != target_cid:
|
||||
self.moveUserToCid(mumble_server, state, target_cid)
|
||||
user.state.channel = target_cid
|
||||
self.users.addOrUpdate(sid, state.session, user)
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def deleteIfUnused(self, mumble_server, cid):
|
||||
"""
|
||||
Takes the cid of a server or team channel and checks if all
|
||||
@ -500,46 +492,46 @@ class source(MumoModule):
|
||||
|
||||
Note: Assumes tree structure
|
||||
"""
|
||||
|
||||
|
||||
sid = mumble_server.id()
|
||||
log = self.log()
|
||||
|
||||
|
||||
result = self.db.channelForCid(sid, cid)
|
||||
if not result:
|
||||
return False
|
||||
|
||||
|
||||
_, _, cur_game, cur_server, cur_team = result
|
||||
assert(cur_game)
|
||||
|
||||
assert cur_game
|
||||
|
||||
if not cur_server:
|
||||
# Don't handle game channels
|
||||
log.debug("(%d) Delete if unused on game channel %d, ignoring", sid, cid)
|
||||
return False
|
||||
|
||||
|
||||
server_channel_cid = None
|
||||
relevant = self.db.channelsFor(sid, cur_game, cur_server)
|
||||
|
||||
|
||||
for _, cur_cid, _, _, cur_team in relevant:
|
||||
if cur_team == self.db.NO_TEAM:
|
||||
server_channel_cid = cur_cid
|
||||
|
||||
|
||||
if self.users.usingChannel(sid, cur_cid):
|
||||
log.debug("(%d) Delete if unused: Channel %d in use", sid, cur_cid)
|
||||
return False # Used
|
||||
|
||||
assert(server_channel_cid != None)
|
||||
|
||||
return False # Used
|
||||
|
||||
assert (server_channel_cid is not None)
|
||||
|
||||
# Unused. Delete server and children
|
||||
log.debug("(%s) Channel %d unused. Will be deleted.", sid, server_channel_cid)
|
||||
mumble_server.removeChannel(server_channel_cid)
|
||||
return True
|
||||
|
||||
def isValidGameType(self, game):
|
||||
return self.cfg().source.gameregex.match(game) != None
|
||||
|
||||
return self.cfg().source.gameregex.match(game) is not None
|
||||
|
||||
def isValidServer(self, game, server):
|
||||
return self.getGameConfig(game, "serverregex").match(server) != None
|
||||
|
||||
return self.getGameConfig(game, "serverregex").match(server) is not None
|
||||
|
||||
def parseSourceContext(self, context):
|
||||
"""
|
||||
Parse source engine context string. Returns tuple with
|
||||
@ -549,19 +541,19 @@ class source(MumoModule):
|
||||
try:
|
||||
prefix, server = context.split('\x00')[0:2]
|
||||
source, game = [s.strip() for s in prefix.split(':', 1)]
|
||||
|
||||
|
||||
if source != "Source engine":
|
||||
# Not a source engine context
|
||||
return (None, None)
|
||||
|
||||
return None, None
|
||||
|
||||
if not self.isValidGameType(game) or not self.isValidServer(game, server):
|
||||
return (None, None)
|
||||
|
||||
return (game, server)
|
||||
|
||||
except (AttributeError, ValueError),e:
|
||||
return (None, None);
|
||||
|
||||
return None, None
|
||||
|
||||
return game, server
|
||||
|
||||
except (AttributeError, ValueError) as e:
|
||||
return None, None
|
||||
|
||||
def parseSourceIdentity(self, identity):
|
||||
"""
|
||||
Parse comma separated source engine identity string key value pairs
|
||||
@ -574,31 +566,32 @@ class source(MumoModule):
|
||||
d = {}
|
||||
for k, v in [var.split(':', 1) for var in identity.split(';')]:
|
||||
d[k] = int(v)
|
||||
|
||||
|
||||
# Make sure mandatory values are present
|
||||
if not "team" in d: return None
|
||||
|
||||
return d
|
||||
if "team" not in d:
|
||||
return None
|
||||
|
||||
return d
|
||||
except (AttributeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
def getGameConfig(self, game, variable):
|
||||
"""
|
||||
Return the game specific value for the given variable if it exists. Otherwise the generic value
|
||||
"""
|
||||
|
||||
|
||||
sectionname = "game:" + game
|
||||
cfg = self.cfg()
|
||||
|
||||
if sectionname not in cfg:
|
||||
return cfg.generic[variable]
|
||||
|
||||
|
||||
return cfg[sectionname][variable]
|
||||
|
||||
|
||||
def dlog(self, sid, state, what, *argc):
|
||||
""" Debug log output helper for user state related things """
|
||||
self.log().debug("(%d) (%d|%d) " + what, sid, state.session, state.userid, *argc)
|
||||
|
||||
|
||||
def handle(self, server, new_state):
|
||||
"""
|
||||
Takes the updated state of the user and collects all
|
||||
@ -607,32 +600,32 @@ class source(MumoModule):
|
||||
"""
|
||||
sid = server.id()
|
||||
session = new_state.session
|
||||
|
||||
|
||||
self.dlog(sid, new_state, "Handle state change")
|
||||
|
||||
|
||||
old_user = self.users.get(sid, session)
|
||||
|
||||
|
||||
if old_user and not old_user.hasContextOrIdentityChanged(new_state):
|
||||
# No change in relevant fields. Simply update state for reference
|
||||
old_user.updateState(new_state)
|
||||
self.dlog(sid, new_state, "State change irrelevant for plugin")
|
||||
return
|
||||
|
||||
|
||||
game, game_server = self.parseSourceContext(new_state.context)
|
||||
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, "Identity: %s -> '%s'", repr(new_state.identity), identity)
|
||||
|
||||
|
||||
updated_user = User(new_state, identity, game, game_server)
|
||||
|
||||
|
||||
self.dlog(sid, new_state, "Starting transition")
|
||||
self.userTransition(server, old_user, updated_user)
|
||||
self.dlog(sid, new_state, "Transition complete")
|
||||
|
||||
|
||||
#
|
||||
#--- Server callback functions
|
||||
# --- Server callback functions
|
||||
#
|
||||
|
||||
|
||||
def userDisconnected(self, server, state, context=None):
|
||||
"""
|
||||
Handle disconnect to be able to delete unused channels
|
||||
@ -640,9 +633,9 @@ class source(MumoModule):
|
||||
"""
|
||||
sid = server.id()
|
||||
session = state.session
|
||||
|
||||
|
||||
self.userTransition(server, self.users.get(sid, session), None)
|
||||
|
||||
|
||||
def userStateChanged(self, server, state, context=None):
|
||||
"""
|
||||
Default state change for user. Could be something uninteresting for
|
||||
@ -650,21 +643,21 @@ class source(MumoModule):
|
||||
string change triggered by starting to play.
|
||||
"""
|
||||
self.handle(server, state)
|
||||
|
||||
|
||||
def userConnected(self, server, state, context=None):
|
||||
"""
|
||||
First time we see the state for a user. userStateChanged behavior
|
||||
applies.
|
||||
"""
|
||||
self.handle(server, state)
|
||||
|
||||
|
||||
def channelRemoved(self, server, state, context=None):
|
||||
"""
|
||||
Updates internal accounting for channels controlled by the plugin.
|
||||
"""
|
||||
cid = state.id
|
||||
sid = server.id()
|
||||
|
||||
|
||||
self.log().debug("(%d) Channel %d removed.", sid, cid)
|
||||
self.db.dropChannel(sid, cid)
|
||||
|
||||
@ -680,17 +673,19 @@ class source(MumoModule):
|
||||
_, _, game, server, team = channel
|
||||
self.db.mapName(name, sid, game, server, team)
|
||||
self.log().debug("(%d) Name mapping for channel %d updated to '%s'", sid, cid, name)
|
||||
|
||||
def userTextMessage(self, server, user, message, current=None): pass
|
||||
def channelCreated(self, server, state, context=None): pass
|
||||
|
||||
|
||||
def userTextMessage(self, server, user, message, current=None):
|
||||
pass
|
||||
|
||||
def channelCreated(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
#
|
||||
#--- Meta callback functions
|
||||
# --- Meta callback functions
|
||||
#
|
||||
|
||||
def started(self, server, context=None):
|
||||
self.log().debug("Started")
|
||||
|
||||
|
||||
def stopped(self, server, context=None):
|
||||
self.log().debug("Stopped")
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,22 +29,25 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import unittest
|
||||
import Queue
|
||||
import config
|
||||
import queue
|
||||
import re
|
||||
import logging
|
||||
import source
|
||||
import unittest
|
||||
|
||||
import config
|
||||
from . import source
|
||||
|
||||
|
||||
class InvalidChannelExceptionMock(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class StateMock():
|
||||
def __init__(self, cid = 0, session = 0, userid = -1):
|
||||
def __init__(self, cid=0, session=0, userid=-1):
|
||||
self.channel = cid
|
||||
self.session = session
|
||||
self.userid = userid
|
||||
|
||||
|
||||
class ChannelStateMock():
|
||||
def __init__(self, cid, name, parent, groups, acls):
|
||||
self.id = cid
|
||||
@ -52,66 +55,68 @@ class ChannelStateMock():
|
||||
self.parent = parent
|
||||
self.groups = groups
|
||||
self.acls = acls
|
||||
|
||||
|
||||
|
||||
class ServerMock():
|
||||
def __init__(self, sid):
|
||||
self.sid = sid
|
||||
self._reset()
|
||||
|
||||
|
||||
def id(self):
|
||||
return self.sid
|
||||
|
||||
|
||||
def _lastChannelID(self):
|
||||
return self.uid
|
||||
|
||||
|
||||
def addChannel(self, name, parent):
|
||||
self.uid += 1
|
||||
assert(not self.uid in self.channels)
|
||||
assert (not self.uid in self.channels)
|
||||
self.channels[self.uid] = ChannelStateMock(self.uid,
|
||||
name,
|
||||
parent,
|
||||
{},
|
||||
[])
|
||||
return self.uid
|
||||
|
||||
|
||||
def addUserToGroup(self, cid, session, group):
|
||||
c = self._getChan(cid)
|
||||
|
||||
|
||||
if session in c.groups:
|
||||
c.groups[session].add(group)
|
||||
else:
|
||||
c.groups[session] = set([group])
|
||||
|
||||
c.groups[session] = {group}
|
||||
|
||||
def _getChan(self, cid):
|
||||
if not cid in self.channels:
|
||||
if cid not in self.channels:
|
||||
raise InvalidChannelExceptionMock()
|
||||
|
||||
|
||||
return self.channels[cid]
|
||||
|
||||
|
||||
def getChannelState(self, cid):
|
||||
return self._getChan(cid)
|
||||
|
||||
|
||||
def setState(self, state):
|
||||
self.user_state.append(state)
|
||||
|
||||
|
||||
def setACL(self, cid, acls, groups, inherit):
|
||||
c = self._getChan(cid)
|
||||
c.acls = acls
|
||||
|
||||
|
||||
def _reset(self):
|
||||
self.uid = 1000
|
||||
self.channels = {} # See addChannel
|
||||
self.channels = {} # See addChannel
|
||||
self.user_state = []
|
||||
|
||||
|
||||
class ACLMock(object):
|
||||
def __init__(self, applyHere, applySubs, userid, group, deny = 0, allow = 0):
|
||||
def __init__(self, applyHere, applySubs, userid, group, deny=0, allow=0):
|
||||
self.applyHere = applyHere
|
||||
self.applySubs = applySubs
|
||||
self.userid = userid
|
||||
self.group = group
|
||||
self.deny = deny
|
||||
self.allow = allow
|
||||
|
||||
|
||||
|
||||
class MurmurMock(object):
|
||||
InvalidChannelException = InvalidChannelExceptionMock
|
||||
@ -120,9 +125,9 @@ class MurmurMock(object):
|
||||
PermissionTraverse = 2
|
||||
PermissionWhisper = 4
|
||||
PermissionSpeak = 8
|
||||
|
||||
|
||||
def _reset(self): pass
|
||||
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@ -132,61 +137,63 @@ class MockACLHelper(object):
|
||||
T = MurmurMock.PermissionTraverse
|
||||
W = MurmurMock.PermissionWhisper
|
||||
S = MurmurMock.PermissionSpeak
|
||||
|
||||
|
||||
EAT = E | T
|
||||
ALL = E|T|W|S
|
||||
ALL = E | T | W | S
|
||||
|
||||
|
||||
ACLS = MockACLHelper
|
||||
|
||||
|
||||
class MetaMock():
|
||||
def __init__(self):
|
||||
#TODO: Create range of server (or even cretae them on demand)
|
||||
self.servers = {1:ServerMock(1),
|
||||
5:ServerMock(5),
|
||||
10:ServerMock(10)}
|
||||
self.s = self.servers[1] # Shorthand
|
||||
|
||||
# TODO: Create range of server (or even cretae them on demand)
|
||||
self.servers = {1: ServerMock(1),
|
||||
5: ServerMock(5),
|
||||
10: ServerMock(10)}
|
||||
self.s = self.servers[1] # Shorthand
|
||||
|
||||
def getServer(self, sid):
|
||||
return self.servers.get(sid, None)
|
||||
|
||||
def _reset(self):
|
||||
for server in self.servers.itervalues():
|
||||
for server in self.servers.values():
|
||||
server._reset()
|
||||
|
||||
|
||||
class ManagerMock():
|
||||
SERVERS_ALL = [-1]
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.q = Queue.Queue()
|
||||
self.q = queue.Queue()
|
||||
self.m = MurmurMock()
|
||||
self.meta = MetaMock()
|
||||
|
||||
|
||||
def getQueue(self):
|
||||
return self.q
|
||||
|
||||
|
||||
def getMurmurModule(self):
|
||||
return self.m
|
||||
|
||||
|
||||
def getMeta(self):
|
||||
return self.meta
|
||||
|
||||
|
||||
def subscribeServerCallbacks(self, callback, servers):
|
||||
self.serverCB = {'callback' : callback, 'servers' : servers}
|
||||
|
||||
self.serverCB = {'callback': callback, 'servers': servers}
|
||||
|
||||
def subscribeMetaCallbacks(self, callback, servers):
|
||||
self.metaCB = {'callback' : callback, 'servers' : servers}
|
||||
|
||||
self.metaCB = {'callback': callback, 'servers': servers}
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def setUp(self):
|
||||
self.mm = ManagerMock();
|
||||
self.mserv = self.mm.meta.getServer(1)
|
||||
|
||||
|
||||
|
||||
testconfig = config.Config(None, source.source.default_config)
|
||||
testconfig.source.database = ":memory:"
|
||||
|
||||
|
||||
# As it is hard to create the read only config structure from
|
||||
# hand use a spare one to steal from
|
||||
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'].serverregex = re.compile("^\[A-1:123\]$")
|
||||
testconfig.__dict__['game:tf'].servername = "Test %(game)s %(server)s"
|
||||
|
||||
|
||||
self.s = source.source("source", self.mm, testconfig)
|
||||
self.mm.s = self.s
|
||||
|
||||
|
||||
# Since we don't want to run threaded if we don't have to
|
||||
# emulate startup to the derived class function
|
||||
self.s.onStart()
|
||||
self.s.connected()
|
||||
|
||||
|
||||
# Critical test assumption
|
||||
self.assertEqual(self.mm.metaCB['callback'], self.s)
|
||||
self.assertEqual(self.mm.serverCB['callback'], self.s)
|
||||
|
||||
|
||||
def resetDB(self):
|
||||
self.s.db.reset()
|
||||
|
||||
|
||||
def resetState(self):
|
||||
self.resetDB()
|
||||
self.mm.m._reset()
|
||||
self.mm.meta._reset()
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
self.s.disconnected()
|
||||
self.s.onStop()
|
||||
|
||||
def testDefaultConfig(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
mm = ManagerMock()
|
||||
INVALIDFORCEDEFAULT = ""
|
||||
s = source.source("source", mm, INVALIDFORCEDEFAULT)
|
||||
self.assertNotEqual(s.cfg(), None)
|
||||
|
||||
|
||||
def testConfiguration(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
# Ensure the default configuration makes sense
|
||||
self.assertEqual(self.mm.serverCB['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().generic.name, "%(game)s")
|
||||
|
||||
@ -243,35 +250,35 @@ class Test(unittest.TestCase):
|
||||
|
||||
def testIdentityParser(self):
|
||||
self.resetState()
|
||||
|
||||
expected = {"universe" : 1,
|
||||
"account_type" : 2,
|
||||
"id" : 3,
|
||||
"instance" : 4,
|
||||
"team" : 5}
|
||||
|
||||
|
||||
expected = {"universe": 1,
|
||||
"account_type": 2,
|
||||
"id": 3,
|
||||
"instance": 4,
|
||||
"team": 5}
|
||||
|
||||
got = self.s.parseSourceIdentity("universe:1;account_type:2;id:00000003;instance:4;team:5")
|
||||
self.assertDictEqual(expected, got)
|
||||
|
||||
|
||||
got = self.s.parseSourceIdentity("universe:1;account_type:2;id:00000003;instance:4;")
|
||||
self.assertEqual(got, None, "Required team variable missing")
|
||||
|
||||
|
||||
self.assertEqual(self.s.parseSourceIdentity(None), None)
|
||||
self.assertEqual(self.s.parseSourceIdentity(""), None)
|
||||
self.assertEqual(self.s.parseSourceIdentity("whatever:4;dskjfskjdfkjsfkjsfkj"), None)
|
||||
|
||||
|
||||
def testContextParser(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
none = (None, None)
|
||||
self.assertEqual(self.s.parseSourceContext(None), none)
|
||||
self.assertEqual(self.s.parseSourceContext(""), none)
|
||||
self.assertEqual(self.s.parseSourceContext("whatever:4;skjdakjkjwqdkjqkj"), none)
|
||||
|
||||
|
||||
expected = ("dod", "[A-1:2807761920(3281)]")
|
||||
actual = self.s.parseSourceContext("Source engine: dod\x00[A-1:2807761920(3281)]\x00")
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
expected = ("dod", "[0:1]")
|
||||
actual = self.s.parseSourceContext("Source engine: dod\x00[0:1]\x00")
|
||||
self.assertEqual(expected, actual)
|
||||
@ -279,266 +286,279 @@ class Test(unittest.TestCase):
|
||||
expected = ("cstrike", "[0:1]")
|
||||
actual = self.s.parseSourceContext("Source engine: cstrike\x00[0:1]\x00")
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
actual = self.s.parseSourceContext("Source engine: fake\x00[A-1:2807761920(3281)]\x00")
|
||||
self.assertEqual(none, actual)
|
||||
|
||||
|
||||
actual = self.s.parseSourceContext("Source engine: cstrike\x0098vcv98re98ver98ver98v\x00")
|
||||
self.assertEqual(none, actual)
|
||||
|
||||
|
||||
# Check alternate serverregex
|
||||
expected = ("tf", "[A-1:123]")
|
||||
actual = self.s.parseSourceContext("Source engine: tf\x00[A-1:123]\x00")
|
||||
self.assertEqual(expected, actual)
|
||||
|
||||
|
||||
actual = self.s.parseSourceContext("Source engine: tf\x00[A-1:2807761920(3281)]\x00")
|
||||
self.assertEqual(none, actual)
|
||||
|
||||
|
||||
def checkACLThings(self, acls, things):
|
||||
self.assertEqual(len(things), len(acls))
|
||||
|
||||
|
||||
i = 0
|
||||
for thing in things:
|
||||
acl = acls[i]
|
||||
for attr, val in thing.iteritems():
|
||||
for attr, val in thing.items():
|
||||
self.assertEqual(getattr(acl, attr), val)
|
||||
i += 1
|
||||
|
||||
|
||||
def testGetOrCreateChannelFor(self):
|
||||
mumble_server = self.mserv
|
||||
|
||||
|
||||
prev = mumble_server._lastChannelID()
|
||||
game = "tf"; server = "[A-1:123]"; team = 3
|
||||
game = "tf"
|
||||
server = "[A-1:123]"
|
||||
team = 3
|
||||
cid = self.s.getOrCreateChannelFor(mumble_server, game, server, team)
|
||||
|
||||
|
||||
self.assertEqual(3, cid - prev)
|
||||
|
||||
|
||||
c = mumble_server.channels
|
||||
|
||||
|
||||
self.assertEqual(c[prev + 1].parent, 0)
|
||||
self.assertEqual(c[prev + 2].parent, prev + 1)
|
||||
self.assertEqual(c[prev + 3].parent, prev + 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 + 3].name, "Red")
|
||||
|
||||
|
||||
sid = mumble_server.id()
|
||||
|
||||
self.assertEqual(self.s.db.cidFor(sid, game), prev + 1);
|
||||
self.assertEqual(self.s.db.cidFor(sid, game, server), prev + 2);
|
||||
self.assertEqual(self.s.db.cidFor(sid, game, server, team), prev + 3);
|
||||
|
||||
|
||||
self.assertEqual(self.s.db.cidFor(sid, game), prev + 1)
|
||||
self.assertEqual(self.s.db.cidFor(sid, game, server), prev + 2)
|
||||
self.assertEqual(self.s.db.cidFor(sid, game, server, team), prev + 3)
|
||||
|
||||
gotcid = self.s.getOrCreateChannelFor(mumble_server, game, server, team)
|
||||
self.assertEqual(cid, gotcid)
|
||||
|
||||
|
||||
c = mumble_server.channels
|
||||
|
||||
self.checkACLThings(c[prev + 3].acls, [{'group' : '~source_tf_[A-1:123]_3'}])
|
||||
self.checkACLThings(c[prev + 2].acls, [{'group' : '~source_tf_[A-1:123]'}])
|
||||
|
||||
self.checkACLThings(c[prev + 3].acls, [{'group': '~source_tf_[A-1:123]_3'}])
|
||||
self.checkACLThings(c[prev + 2].acls, [{'group': '~source_tf_[A-1:123]'}])
|
||||
self.checkACLThings(c[prev + 1].acls, [{},
|
||||
{'group' : '~source_tf'}])
|
||||
|
||||
#print self.s.db.db.execute("SELECT * FROM source").fetchall()
|
||||
|
||||
{'group': '~source_tf'}])
|
||||
|
||||
# print self.s.db.db.execute("SELECT * FROM source").fetchall()
|
||||
|
||||
def testGetGameName(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
self.assertEqual(self.s.getGameName("tf"), "Team Fortress 2")
|
||||
self.assertEqual(self.s.getGameName("invalid"), "%(game)s");
|
||||
|
||||
self.assertEqual(self.s.getGameName("invalid"), "%(game)s")
|
||||
|
||||
def testGetServerName(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
self.assertEqual(self.s.getServerName("tf"), "Test %(game)s %(server)s")
|
||||
self.assertEqual(self.s.getServerName("invalid"), "%(server)s");
|
||||
|
||||
self.assertEqual(self.s.getServerName("invalid"), "%(server)s")
|
||||
|
||||
def testGetTeamName(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
self.assertEqual(self.s.getTeamName("tf", 2), "Blue")
|
||||
self.assertEqual(self.s.getTeamName("tf", 100), "100") #oob
|
||||
|
||||
self.assertEqual(self.s.getTeamName("tf", 100), "100") # oob
|
||||
|
||||
self.assertEqual(self.s.getTeamName("invalid", 2), "Team one")
|
||||
self.assertEqual(self.s.getTeamName("invalid", 100), "100") #oob
|
||||
|
||||
self.assertEqual(self.s.getTeamName("invalid", 100), "100") # oob
|
||||
|
||||
def testValidGameType(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
self.assertTrue(self.s.isValidGameType("dod"))
|
||||
self.assertTrue(self.s.isValidGameType("cstrike"))
|
||||
self.assertTrue(self.s.isValidGameType("tf"))
|
||||
|
||||
|
||||
self.assertFalse(self.s.isValidGameType("dodx"))
|
||||
self.assertFalse(self.s.isValidGameType("xdod"))
|
||||
self.assertFalse(self.s.isValidGameType(""))
|
||||
|
||||
|
||||
def testValidServer(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
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.assertTrue(self.s.isValidServer("tf", "[A-1:123]"))
|
||||
|
||||
|
||||
self.assertFalse(self.s.isValidServer("tf", "x[A-1:123]"))
|
||||
self.assertFalse(self.s.isValidServer("tf", "[A-1:123]x"))
|
||||
|
||||
|
||||
def testMoveUser(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
mumble_server = self.mserv
|
||||
user_state = StateMock()
|
||||
prev = self.mserv._lastChannelID()
|
||||
|
||||
|
||||
TEAM_BLUE = 2
|
||||
TEAM_RED = 3
|
||||
|
||||
|
||||
BASE_SID = 0
|
||||
GAME_SID = prev + 1
|
||||
SERVER_SID = prev + 2
|
||||
TEAM_RED_SID = prev + 3
|
||||
TEAM_BLUE_SID = prev + 4
|
||||
|
||||
user = source.User(user_state, {'team':TEAM_BLUE}, "tf", "[A-1:123]")
|
||||
|
||||
user = source.User(user_state, {'team': TEAM_BLUE}, "tf", "[A-1:123]")
|
||||
self.s.moveUser(self.mserv, user)
|
||||
c = mumble_server.channels
|
||||
self.assertEqual(c[prev + 1].parent, BASE_SID)
|
||||
self.assertEqual(c[prev + 2].parent, GAME_SID)
|
||||
self.assertEqual(c[prev + 3].parent, SERVER_SID)
|
||||
|
||||
|
||||
self.assertEqual(c[prev + 1].name, "Team Fortress 2")
|
||||
self.assertEqual(c[prev + 2].name, "Test tf [A-1:123]")
|
||||
self.assertEqual(c[prev + 3].name, "Blue")
|
||||
self.assertEqual(len(c), 3)
|
||||
|
||||
|
||||
self.assertEqual(user_state.channel, TEAM_RED_SID)
|
||||
self.assertEqual(mumble_server.user_state[0], user_state)
|
||||
|
||||
user.identity['team'] = TEAM_RED
|
||||
self.s.moveUser(self.mserv, user)
|
||||
|
||||
|
||||
self.assertEqual(c[prev + 4].parent, SERVER_SID)
|
||||
self.assertEqual(c[prev + 4].name, "Red")
|
||||
self.assertEqual(len(c), 4)
|
||||
|
||||
|
||||
self.assertEqual(user_state.channel, TEAM_BLUE_SID)
|
||||
self.assertEqual(mumble_server.user_state[0], user_state)
|
||||
|
||||
|
||||
def testValidateChannelDB(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
self.s.db.registerChannel(5, 6, "7")
|
||||
self.s.db.registerChannel(5, 7, "7", "8")
|
||||
self.s.db.registerChannel(5, 8, "7", "8", 9)
|
||||
self.s.db.registerChannel(6, 9, "8", "9", 10)
|
||||
self.s.db.registerChannel(5, 10, "7", "123", 9)
|
||||
|
||||
game = 'cstrike'; server = '[A123:123]'; team = 1
|
||||
|
||||
game = 'cstrike'
|
||||
server = '[A123:123]'
|
||||
team = 1
|
||||
self.s.getOrCreateChannelFor(self.mserv, game, server, team)
|
||||
self.s.validateChannelDB()
|
||||
self.assertEqual(len(self.s.db.registeredChannels()), 3)
|
||||
|
||||
|
||||
|
||||
def testSetACLsForGameChannel(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
mumble_server = self.mserv
|
||||
cid = mumble_server.addChannel("test", 1); game = "dod"
|
||||
|
||||
cid = mumble_server.addChannel("test", 1)
|
||||
game = "dod"
|
||||
|
||||
self.s.setACLsForGameChannel(mumble_server, cid, game)
|
||||
acls = mumble_server.channels[cid].acls
|
||||
|
||||
self.checkACLThings(acls, [{'applyHere' : True,
|
||||
'applySubs' : True,
|
||||
'userid' : -1,
|
||||
'group' : 'all',
|
||||
'deny' : ACLS.ALL,
|
||||
'allow' : 0},
|
||||
|
||||
{'applyHere' : True,
|
||||
'applySubs' : False,
|
||||
'userid' : -1,
|
||||
'group' : '~source_dod',
|
||||
'deny' : 0,
|
||||
'allow' : ACLS.EAT}])
|
||||
|
||||
|
||||
|
||||
self.checkACLThings(acls, [{'applyHere': True,
|
||||
'applySubs': True,
|
||||
'userid': -1,
|
||||
'group': 'all',
|
||||
'deny': ACLS.ALL,
|
||||
'allow': 0},
|
||||
|
||||
{'applyHere': True,
|
||||
'applySubs': False,
|
||||
'userid': -1,
|
||||
'group': '~source_dod',
|
||||
'deny': 0,
|
||||
'allow': ACLS.EAT}])
|
||||
|
||||
def testSetACLsForServerChannel(self):
|
||||
self.resetState()
|
||||
|
||||
mumble_server = self.mserv
|
||||
cid = mumble_server.addChannel("test", 1); game = "tf"; server = "[A-1:SomeServer]"
|
||||
cid = mumble_server.addChannel("test", 1)
|
||||
game = "tf"
|
||||
server = "[A-1:SomeServer]"
|
||||
self.s.setACLsForServerChannel(mumble_server, cid, game, server)
|
||||
acls = mumble_server.channels[cid].acls
|
||||
|
||||
self.checkACLThings(acls, [{'applyHere' : True,
|
||||
'applySubs' : False,
|
||||
'userid' : -1,
|
||||
'group' : '~source_tf_[A-1:SomeServer]',
|
||||
'deny' : 0,
|
||||
'allow' : ACLS.EAT}])
|
||||
|
||||
|
||||
|
||||
self.checkACLThings(acls, [{'applyHere': True,
|
||||
'applySubs': False,
|
||||
'userid': -1,
|
||||
'group': '~source_tf_[A-1:SomeServer]',
|
||||
'deny': 0,
|
||||
'allow': ACLS.EAT}])
|
||||
|
||||
def testSetACLsForTeamChannel(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
mumble_server = self.mserv
|
||||
cid = mumble_server.addChannel("test", 1); game = "tf"; server = "[A-1:SomeServer]"; team = 2
|
||||
|
||||
cid = mumble_server.addChannel("test", 1)
|
||||
game = "tf"
|
||||
server = "[A-1:SomeServer]"
|
||||
team = 2
|
||||
|
||||
self.s.setACLsForTeamChannel(mumble_server, cid, game, server, team)
|
||||
acls = mumble_server.channels[cid].acls
|
||||
|
||||
self.checkACLThings(acls, [{'applyHere' : True,
|
||||
'applySubs' : False,
|
||||
'userid' : -1,
|
||||
'group' : '~source_tf_[A-1:SomeServer]_2',
|
||||
'deny' : 0,
|
||||
'allow' : ACLS.ALL}])
|
||||
|
||||
|
||||
self.checkACLThings(acls, [{'applyHere': True,
|
||||
'applySubs': False,
|
||||
'userid': -1,
|
||||
'group': '~source_tf_[A-1:SomeServer]_2',
|
||||
'deny': 0,
|
||||
'allow': ACLS.ALL}])
|
||||
|
||||
def testAddToGroups(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
mumble_server = self.mserv
|
||||
prev = mumble_server._lastChannelID()
|
||||
|
||||
session = 10; game = 'cstrike'; server = '[A-1:12345]'; team = 1
|
||||
|
||||
session = 10
|
||||
game = 'cstrike'
|
||||
server = '[A-1:12345]'
|
||||
team = 1
|
||||
self.s.getOrCreateChannelFor(mumble_server, game, server, team)
|
||||
|
||||
|
||||
# Test
|
||||
self.s.addToGroups(mumble_server, session, game, server, team)
|
||||
|
||||
|
||||
groups = mumble_server.channels[prev + 1].groups[session]
|
||||
self.assertIn("source_cstrike", groups)
|
||||
self.assertIn("source_cstrike_[A-1:12345]", groups)
|
||||
self.assertIn("source_cstrike_[A-1:12345]_1", groups)
|
||||
|
||||
|
||||
def testChannelNameMapping(self):
|
||||
self.resetState()
|
||||
|
||||
|
||||
mumble_server = self.mserv
|
||||
|
||||
game = 'cstrike'; server = '[A-1:12345]'; team = 1
|
||||
|
||||
game = 'cstrike'
|
||||
server = '[A-1:12345]'
|
||||
team = 1
|
||||
self.s.getOrCreateChannelFor(mumble_server, game, server, team)
|
||||
cids = []
|
||||
for c in mumble_server.channels.itervalues():
|
||||
for c in mumble_server.channels.values():
|
||||
c.name = str(c.id)
|
||||
self.s.channelStateChanged(mumble_server, c)
|
||||
cids.append(c.id)
|
||||
|
||||
|
||||
mumble_server._reset()
|
||||
self.s.validateChannelDB()
|
||||
self.assertEqual(len(mumble_server.channels), 0)
|
||||
self.assertEqual(len(self.s.db.registeredChannels()), 0)
|
||||
|
||||
|
||||
self.s.getOrCreateChannelFor(mumble_server, game, server, team)
|
||||
for cid in cids:
|
||||
self.assertEqual(mumble_server._getChan(cid).name, str(cid))
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#logging.basicConfig(level = logging.DEBUG)
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
# logging.basicConfig(level = logging.DEBUG)
|
||||
# import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -34,31 +34,32 @@ class User(object):
|
||||
User to hold state as well as parsed data fields in a
|
||||
sane fashion.
|
||||
"""
|
||||
|
||||
def __init__(self, state, identity=None, game=None, server=None):
|
||||
self.state = state
|
||||
self.identity = identity or {}
|
||||
self.server = server
|
||||
self.game = game
|
||||
|
||||
|
||||
def valid(self):
|
||||
"""
|
||||
True if valid data is available for all fields
|
||||
"""
|
||||
return self.state and self.identity and self.server and self.game
|
||||
|
||||
|
||||
def hasContextOrIdentityChanged(self, otherstate):
|
||||
"""
|
||||
Checks whether the given state diverges from this users's
|
||||
"""
|
||||
return self.state.context != otherstate.context or \
|
||||
self.state.identity != otherstate.identity
|
||||
|
||||
self.state.identity != otherstate.identity
|
||||
|
||||
def updateState(self, state):
|
||||
"""
|
||||
Updates the state of this user
|
||||
"""
|
||||
self.state = state
|
||||
|
||||
|
||||
def updateData(self, identity, game, server):
|
||||
"""
|
||||
Updates the data fields for this user
|
||||
@ -66,16 +67,17 @@ class User(object):
|
||||
self.identity = identity
|
||||
self.game = game
|
||||
self.server = server
|
||||
|
||||
|
||||
|
||||
class UserRegistry(object):
|
||||
"""
|
||||
Registry to store User objects for given servers
|
||||
and sessions.
|
||||
"""
|
||||
|
||||
|
||||
def __init__(self):
|
||||
self.users = {} # {session:user, ...}
|
||||
|
||||
self.users = {} # {session:user, ...}
|
||||
|
||||
def get(self, sid, session):
|
||||
"""
|
||||
Return user or None from registry
|
||||
@ -84,34 +86,34 @@ class UserRegistry(object):
|
||||
return self.users[sid][session]
|
||||
except KeyError:
|
||||
return None
|
||||
|
||||
|
||||
def add(self, sid, session, user):
|
||||
"""
|
||||
Add new user to registry
|
||||
"""
|
||||
assert(isinstance(user, User))
|
||||
|
||||
assert (isinstance(user, User))
|
||||
|
||||
if not sid in self.users:
|
||||
self.users[sid] = {session:user}
|
||||
self.users[sid] = {session: user}
|
||||
elif not session in self.users[sid]:
|
||||
self.users[sid][session] = user
|
||||
else:
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def addOrUpdate(self, sid, session, user):
|
||||
"""
|
||||
Add user or overwrite existing one (identified by sid + session)
|
||||
"""
|
||||
assert(isinstance(user, User))
|
||||
|
||||
assert (isinstance(user, User))
|
||||
|
||||
if not sid in self.users:
|
||||
self.users[sid] = {session:user}
|
||||
self.users[sid] = {session: user}
|
||||
else:
|
||||
self.users[sid][session] = user
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def remove(self, sid, session):
|
||||
"""
|
||||
Remove user from registry
|
||||
@ -120,15 +122,14 @@ class UserRegistry(object):
|
||||
del self.users[sid][session]
|
||||
except KeyError:
|
||||
return False
|
||||
return True
|
||||
|
||||
return True
|
||||
|
||||
def usingChannel(self, sid, cid):
|
||||
"""
|
||||
Return true if any user in the registry is occupying the given channel
|
||||
"""
|
||||
for user in self.users[sid].itervalues():
|
||||
for user in self.users[sid].values():
|
||||
if user.state and user.state.channel == cid:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
return False
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2013 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -31,51 +31,55 @@
|
||||
|
||||
import unittest
|
||||
|
||||
from users import User, UserRegistry
|
||||
from .users import User, UserRegistry
|
||||
|
||||
|
||||
class Test(unittest.TestCase):
|
||||
|
||||
def getSomeUsers(self, n =5):
|
||||
sid = []; session = []; user = []
|
||||
def getSomeUsers(self, n=5):
|
||||
sid = []
|
||||
session = []
|
||||
user = []
|
||||
for i in range(n):
|
||||
s=str(i)
|
||||
sid.append(i) ; session.append(i)
|
||||
user.append(User("state"+s, "identity"+s, "game"+s, "server"+s))
|
||||
|
||||
s = str(i)
|
||||
sid.append(i)
|
||||
session.append(i)
|
||||
user.append(User("state" + s, "identity" + s, "game" + s, "server" + s))
|
||||
|
||||
return sid, session, user
|
||||
|
||||
def testRegistryCRUDOps(self):
|
||||
r = UserRegistry()
|
||||
|
||||
|
||||
sid, session, user = self.getSomeUsers()
|
||||
|
||||
|
||||
# Create & Read
|
||||
self.assertTrue(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.assertTrue(r.addOrUpdate(sid[1], session[1], user[1]))
|
||||
self.assertEqual(r.get(sid[1], session[1]), user[1])
|
||||
|
||||
|
||||
# Update
|
||||
self.assertTrue(r.addOrUpdate(sid[0], session[0], user[2]))
|
||||
self.assertEqual(r.get(sid[0], session[0]), user[2])
|
||||
|
||||
|
||||
# Delete
|
||||
self.assertTrue(r.remove(sid[1], session[1]))
|
||||
self.assertFalse(r.remove(sid[1], session[1]))
|
||||
self.assertEqual(r.get(sid[1], session[1]), None)
|
||||
|
||||
|
||||
self.assertTrue(r.remove(sid[0], session[0]))
|
||||
self.assertFalse(r.remove(sid[0], session[0]))
|
||||
self.assertEqual(r.get(sid[0], session[0]), None)
|
||||
|
||||
|
||||
def testUser(self):
|
||||
u = User("State", {'team':2} , "tf", "Someserver")
|
||||
u = User("State", {'team': 2}, "tf", "Someserver")
|
||||
self.assertTrue(u.valid())
|
||||
self.assertFalse(User("State").valid())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
# import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010-2011 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -36,22 +36,20 @@
|
||||
# debugging purposes. Usually you don't want
|
||||
# to use this.
|
||||
#
|
||||
from mumo_module import MumoModule, logModFu
|
||||
|
||||
|
||||
from mumo_module import (x2bool,
|
||||
MumoModule,
|
||||
logModFu)
|
||||
|
||||
class test(MumoModule):
|
||||
default_config = {'testing':(('tvar', int , 1),
|
||||
('novar', str, 'no bernd'))}
|
||||
|
||||
def __init__(self, name, manager, configuration = None):
|
||||
default_config = {'testing': (('tvar', int, 1),
|
||||
('novar', str, 'no bernd'))}
|
||||
|
||||
def __init__(self, name, manager, configuration=None):
|
||||
MumoModule.__init__(self, name, manager, configuration)
|
||||
log = self.log()
|
||||
cfg = self.cfg()
|
||||
log.debug("tvar: %s", cfg.testing.tvar)
|
||||
log.debug("novar: %s", cfg.testing.novar)
|
||||
|
||||
|
||||
@logModFu
|
||||
def connected(self):
|
||||
manager = self.manager()
|
||||
@ -60,56 +58,57 @@ class test(MumoModule):
|
||||
manager.subscribeMetaCallbacks(self)
|
||||
manager.subscribeServerCallbacks(self, manager.SERVERS_ALL)
|
||||
manager.subscribeContextCallbacks(self, manager.SERVERS_ALL)
|
||||
|
||||
|
||||
@logModFu
|
||||
def disconnected(self):
|
||||
self.log().debug("Ice disconnected")
|
||||
|
||||
#
|
||||
#--- Meta callback functions
|
||||
# --- Meta callback functions
|
||||
#
|
||||
|
||||
|
||||
@logModFu
|
||||
def started(self, server, context = None):
|
||||
def started(self, server, context=None):
|
||||
pass
|
||||
|
||||
|
||||
@logModFu
|
||||
def stopped(self, server, context = None):
|
||||
def stopped(self, server, context=None):
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
#--- Server callback functions
|
||||
# --- Server callback functions
|
||||
#
|
||||
@logModFu
|
||||
def userConnected(self, server, state, context = None):
|
||||
def userConnected(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
|
||||
@logModFu
|
||||
def userDisconnected(self, server, state, context = None):
|
||||
def userDisconnected(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
|
||||
@logModFu
|
||||
def userStateChanged(self, server, state, context = None):
|
||||
def userStateChanged(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
|
||||
@logModFu
|
||||
def userTextMessage(self, server, user, message, current=None):
|
||||
pass
|
||||
|
||||
|
||||
@logModFu
|
||||
def channelCreated(self, server, state, context = None):
|
||||
def channelCreated(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
|
||||
@logModFu
|
||||
def channelRemoved(self, server, state, context = None):
|
||||
def channelRemoved(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
|
||||
@logModFu
|
||||
def channelStateChanged(self, server, state, context = None):
|
||||
def channelStateChanged(self, server, state, context=None):
|
||||
pass
|
||||
|
||||
|
||||
#
|
||||
#--- Server context callback functions
|
||||
# --- Server context callback functions
|
||||
#
|
||||
@logModFu
|
||||
def contextAction(self, server, action, user, session, channelid, context = None):
|
||||
pass
|
||||
def contextAction(self, server, action, user, session, channelid, context=None):
|
||||
pass
|
||||
|
147
mumo.py
147
mumo.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010-2013 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,50 +29,51 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
import tempfile
|
||||
from logging import (debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
critical,
|
||||
exception,
|
||||
getLogger)
|
||||
from optparse import OptionParser
|
||||
from threading import Timer
|
||||
|
||||
import Ice
|
||||
import IcePy
|
||||
import logging
|
||||
import tempfile
|
||||
from config import (Config,
|
||||
x2bool,
|
||||
commaSeperatedIntegers)
|
||||
|
||||
from threading import Timer
|
||||
from optparse import OptionParser
|
||||
from logging import (debug,
|
||||
info,
|
||||
warning,
|
||||
error,
|
||||
critical,
|
||||
exception,
|
||||
getLogger)
|
||||
from config import (Config,
|
||||
commaSeperatedIntegers)
|
||||
from mumo_manager import MumoManager
|
||||
|
||||
#
|
||||
#--- Default configuration values
|
||||
# --- Default configuration values
|
||||
#
|
||||
cfgfile = 'mumo.ini'
|
||||
default = MumoManager.cfg_default.copy()
|
||||
default.update({'ice':(('host', str, '127.0.0.1'),
|
||||
('port', int, 6502),
|
||||
('slice', str, ''),
|
||||
('secret', str, ''),
|
||||
('slicedirs', str, '/usr/share/slice;/usr/share/Ice/slice'),
|
||||
('watchdog', int, 30),
|
||||
('callback_host', str, '127.0.0.1'),
|
||||
('callback_port', int, -1)),
|
||||
default.update({'ice': (('host', str, '127.0.0.1'),
|
||||
('port', int, 6502),
|
||||
('slice', str, ''),
|
||||
('secret', str, ''),
|
||||
('slicedirs', str, '/usr/share/slice;/usr/share/Ice/slice'),
|
||||
('watchdog', int, 30),
|
||||
('callback_host', str, '127.0.0.1'),
|
||||
('callback_port', int, -1)),
|
||||
|
||||
'iceraw': None,
|
||||
'murmur': (('servers', commaSeperatedIntegers, []),),
|
||||
'system': (('pidfile', str, 'mumo.pid'),),
|
||||
'log': (('level', int, logging.DEBUG),
|
||||
('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):
|
||||
#
|
||||
#--- Loads a given slicefile, used by dynload_slice and fsload_slice
|
||||
# --- Loads a given slicefile, used by dynload_slice and fsload_slice
|
||||
# This function works around a number of differences between Ice python
|
||||
# versions and distributions when it comes to slice include directories.
|
||||
#
|
||||
@ -89,9 +90,10 @@ def load_slice(slice):
|
||||
|
||||
Ice.loadSlice('', slicedirs + [slice])
|
||||
|
||||
|
||||
def dynload_slice(prx):
|
||||
#
|
||||
#--- Dynamically retrieves the slice file from the target server
|
||||
# --- Dynamically retrieves the slice file from the target server
|
||||
#
|
||||
info("Loading slice from server")
|
||||
try:
|
||||
@ -99,23 +101,25 @@ def dynload_slice(prx):
|
||||
# In case it breaks with future versions use slice2py and search for
|
||||
# "IcePy.Operation('getSlice'," for updates in the generated bindings.
|
||||
op = None
|
||||
if IcePy.intVersion() < 30500L:
|
||||
if IcePy.intVersion() < 30500:
|
||||
# Old 3.4 signature with 9 parameters
|
||||
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, (), (), (), IcePy._t_string, ())
|
||||
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, (), (),
|
||||
(), IcePy._t_string, ())
|
||||
|
||||
else:
|
||||
# New 3.5 signature with 10 parameters.
|
||||
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, None, (), (), (), ((), IcePy._t_string, False, 0), ())
|
||||
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, None, (),
|
||||
(), (), ((), IcePy._t_string, False, 0), ())
|
||||
|
||||
slice = op.invoke(prx, ((), None))
|
||||
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix = '.ice')
|
||||
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix='.ice')
|
||||
dynslicefile = os.fdopen(dynslicefiledesc, 'w')
|
||||
dynslicefile.write(slice)
|
||||
dynslicefile.flush()
|
||||
load_slice(dynslicefilepath)
|
||||
dynslicefile.close()
|
||||
os.remove(dynslicefilepath)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
error("Retrieving slice from server failed")
|
||||
exception(e)
|
||||
raise
|
||||
@ -123,14 +127,15 @@ def dynload_slice(prx):
|
||||
|
||||
def fsload_slice(slice):
|
||||
#
|
||||
#--- Load slice from file system
|
||||
# --- Load slice from file system
|
||||
#
|
||||
debug("Loading slice from filesystem: %s" % slice)
|
||||
load_slice(slice)
|
||||
|
||||
|
||||
def do_main_program():
|
||||
#
|
||||
#--- Moderator implementation
|
||||
# --- Moderator implementation
|
||||
# All of this has to go in here so we can correctly daemonize the tool
|
||||
# without loosing the file descriptors opened by the Ice module
|
||||
|
||||
@ -153,6 +158,7 @@ def do_main_program():
|
||||
else:
|
||||
fsload_slice(cfg.ice.slice)
|
||||
|
||||
# noinspection PyUnresolvedReferences
|
||||
import Murmur
|
||||
|
||||
class mumoIceApp(Ice.Application):
|
||||
@ -201,7 +207,8 @@ def do_main_program():
|
||||
else:
|
||||
cbp = ''
|
||||
|
||||
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client', 'tcp -h %s%s' % (cfg.ice.callback_host, cbp))
|
||||
adapter = ice.createObjectAdapterWithEndpoints('Callback.Client',
|
||||
'tcp -h %s%s' % (cfg.ice.callback_host, cbp))
|
||||
adapter.activate()
|
||||
self.adapter = adapter
|
||||
self.manager.setClientAdapter(adapter)
|
||||
@ -230,11 +237,12 @@ def do_main_program():
|
||||
servercb = Murmur.ServerCallbackPrx.uncheckedCast(servercbprx)
|
||||
server.addCallback(servercb)
|
||||
|
||||
except (Murmur.InvalidSecretException, Ice.UnknownUserException, Ice.ConnectionRefusedException), e:
|
||||
except (Murmur.InvalidSecretException, Ice.UnknownUserException, Ice.ConnectionRefusedException) as e:
|
||||
if isinstance(e, Ice.ConnectionRefusedException):
|
||||
error('Server refused connection')
|
||||
elif isinstance(e, Murmur.InvalidSecretException) or \
|
||||
isinstance(e, Ice.UnknownUserException) and (e.unknown == 'Murmur::InvalidSecretException'):
|
||||
isinstance(e, Ice.UnknownUserException) and (
|
||||
e.unknown == 'Murmur::InvalidSecretException'):
|
||||
error('Invalid ice secret')
|
||||
else:
|
||||
# We do not actually want to handle this one, re-raise it
|
||||
@ -253,7 +261,7 @@ def do_main_program():
|
||||
Tries to retrieve the server uptime to determine wheter the server is
|
||||
still responsive or has restarted in the meantime
|
||||
"""
|
||||
#debug('Watchdog run')
|
||||
# debug('Watchdog run')
|
||||
try:
|
||||
uptime = self.meta.getUptime()
|
||||
if self.metaUptime > 0:
|
||||
@ -266,8 +274,9 @@ def do_main_program():
|
||||
self.attachCallbacks()
|
||||
|
||||
self.metaUptime = uptime
|
||||
except Ice.Exception, e:
|
||||
error('Connection to server lost, will try to reestablish callbacks in next watchdog run (%ds)', cfg.ice.watchdog)
|
||||
except Ice.Exception as e:
|
||||
error('Connection to server lost, will try to reestablish callbacks in next watchdog run (%ds)',
|
||||
cfg.ice.watchdog)
|
||||
debug(str(e))
|
||||
self.attachCallbacks()
|
||||
|
||||
@ -306,11 +315,12 @@ def do_main_program():
|
||||
|
||||
The default is to catch all non-Ice exceptions.
|
||||
"""
|
||||
|
||||
def newdec(func):
|
||||
def newfunc(*args, **kws):
|
||||
try:
|
||||
return func(*args, **kws)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
catch = True
|
||||
for ex in exceptions:
|
||||
if isinstance(e, ex):
|
||||
@ -324,6 +334,7 @@ def do_main_program():
|
||||
raise
|
||||
|
||||
return newfunc
|
||||
|
||||
return newdec
|
||||
|
||||
class metaCallback(Murmur.MetaCallback):
|
||||
@ -347,7 +358,7 @@ def do_main_program():
|
||||
server.addCallback(servercb)
|
||||
|
||||
# Apparently this server was restarted without us noticing
|
||||
except (Murmur.InvalidSecretException, Ice.UnknownUserException), e:
|
||||
except (Murmur.InvalidSecretException, Ice.UnknownUserException) as e:
|
||||
if hasattr(e, "unknown") and e.unknown != "Murmur::InvalidSecretException":
|
||||
# Special handling for Murmur 1.2.2 servers with invalid slice files
|
||||
raise e
|
||||
@ -382,10 +393,10 @@ def do_main_program():
|
||||
|
||||
debug('Server shutdown stopped a virtual server')
|
||||
|
||||
|
||||
def forwardServer(fu):
|
||||
def new_fu(self, *args, **kwargs):
|
||||
self.manager.announceServer(self.sid, fu.__name__, self.server, *args, **kwargs)
|
||||
|
||||
return new_fu
|
||||
|
||||
class serverCallback(Murmur.ServerCallback):
|
||||
@ -405,24 +416,30 @@ def do_main_program():
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def userStateChanged(self, u, current=None): pass
|
||||
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def userDisconnected(self, u, current=None): pass
|
||||
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def userConnected(self, u, current=None): pass
|
||||
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def channelCreated(self, c, current=None): pass
|
||||
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def channelRemoved(self, c, current=None): pass
|
||||
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def channelStateChanged(self, c, current=None): pass
|
||||
|
||||
@checkSecret
|
||||
@forwardServer
|
||||
def userTextMessage(self, u, m, current=None) : pass
|
||||
def userTextMessage(self, u, m, current=None): pass
|
||||
|
||||
class customContextCallback(Murmur.ServerContextCallback):
|
||||
def __init__(self, contextActionCallback, *ctx):
|
||||
@ -436,7 +453,7 @@ def do_main_program():
|
||||
self.cb(*(self.ctx + args), **argv)
|
||||
|
||||
#
|
||||
#--- Start of moderator
|
||||
# --- Start of moderator
|
||||
#
|
||||
info('Starting mumble moderator')
|
||||
debug('Initializing manager')
|
||||
@ -454,6 +471,7 @@ def do_main_program():
|
||||
info('Shutdown complete')
|
||||
return state
|
||||
|
||||
|
||||
class CustomLogger(Ice.Logger):
|
||||
"""
|
||||
Logger implementation to pipe Ice log messages into
|
||||
@ -476,8 +494,9 @@ class CustomLogger(Ice.Logger):
|
||||
def error(self, message):
|
||||
self._log.error(message)
|
||||
|
||||
|
||||
#
|
||||
#--- Start of program
|
||||
# --- Start of program
|
||||
#
|
||||
if __name__ == '__main__':
|
||||
# Parse commandline options
|
||||
@ -501,22 +520,21 @@ if __name__ == '__main__':
|
||||
# Load configuration
|
||||
try:
|
||||
cfg = Config(option.ini, default)
|
||||
except Exception, e:
|
||||
print >> sys.stderr, 'Fatal error, could not load config file from "%s"' % cfgfile
|
||||
print >> sys.stderr, e
|
||||
except Exception as e:
|
||||
print('Fatal error, could not load config file from "%s"' % cfgfile, file=sys.stderr)
|
||||
print(e, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
# Initialise logger
|
||||
if cfg.log.file:
|
||||
try:
|
||||
logfile = open(cfg.log.file, 'a')
|
||||
except IOError, e:
|
||||
#print>>sys.stderr, str(e)
|
||||
print >> sys.stderr, 'Fatal error, could not open logfile "%s"' % cfg.log.file
|
||||
except IOError as e:
|
||||
# print>>sys.stderr, str(e)
|
||||
print('Fatal error, could not open logfile "%s"' % cfg.log.file, file=sys.stderr)
|
||||
sys.exit(1)
|
||||
else:
|
||||
logfile = logging.sys.stderr
|
||||
|
||||
logfile = logging.sys.stdout
|
||||
|
||||
if option.verbose:
|
||||
level = cfg.log.level
|
||||
@ -531,16 +549,17 @@ if __name__ == '__main__':
|
||||
# unless the user explicitly defined what he expected with the -a / -d parameter.
|
||||
try:
|
||||
if option.force_app:
|
||||
raise ImportError # Pretend that we couldn't import the daemon lib
|
||||
raise ImportError # Pretend that we couldn't import the daemon lib
|
||||
import daemon
|
||||
|
||||
try:
|
||||
from daemon.pidfile import TimeoutPIDLockFile
|
||||
except ImportError: # Version < 1.6
|
||||
except ImportError: # Version < 1.6
|
||||
from daemon.pidlockfile import TimeoutPIDLockFile
|
||||
except ImportError:
|
||||
if option.force_daemon:
|
||||
print >> sys.stderr, 'Fatal error, could not daemonize process due to missing "daemon" library, ' \
|
||||
'please install the missing dependency and restart the application'
|
||||
print('Fatal error, could not daemonize process due to missing "daemon" library, '
|
||||
'please install the missing dependency and restart the application', file=sys.stderr)
|
||||
sys.exit(1)
|
||||
ret = do_main_program()
|
||||
else:
|
||||
@ -548,10 +567,10 @@ if __name__ == '__main__':
|
||||
if pidfile.is_locked():
|
||||
try:
|
||||
os.kill(pidfile.read_pid(), 0)
|
||||
print >> sys.stderr, 'Mumo already running as %s' % pidfile.read_pid()
|
||||
print('Mumo already running as %s' % pidfile.read_pid(), file=sys.stderr)
|
||||
sys.exit(1)
|
||||
except OSError:
|
||||
print >> sys.stderr, 'Found stale mumo pid file but no process, breaking lock'
|
||||
print('Found stale mumo pid file but no process, breaking lock', file=sys.stderr)
|
||||
pidfile.break_lock()
|
||||
|
||||
context = daemon.DaemonContext(working_directory=sys.path[0],
|
||||
|
125
mumo_manager.py
125
mumo_manager.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,31 +29,37 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import Queue
|
||||
from worker import Worker, local_thread, local_thread_blocking
|
||||
from config import Config
|
||||
import sys
|
||||
import os
|
||||
import queue
|
||||
import sys
|
||||
import uuid
|
||||
|
||||
from config import Config
|
||||
from worker import Worker, local_thread, local_thread_blocking
|
||||
|
||||
|
||||
class FailedLoadModuleException(Exception):
|
||||
pass
|
||||
|
||||
|
||||
class FailedLoadModuleConfigException(FailedLoadModuleException):
|
||||
pass
|
||||
|
||||
|
||||
class FailedLoadModuleImportException(FailedLoadModuleException):
|
||||
pass
|
||||
|
||||
|
||||
class FailedLoadModuleInitializationException(FailedLoadModuleException):
|
||||
pass
|
||||
|
||||
def debug_log(enable = True):
|
||||
|
||||
def debug_log(enable=True):
|
||||
def new_dec(fu):
|
||||
def new_fu(*args, **kwargs):
|
||||
self = args[0]
|
||||
log = self.log()
|
||||
skwargs = ','.join(['%s=%s' % (karg,repr(arg)) for karg, arg in kwargs])
|
||||
skwargs = ','.join(['%s=%s' % (karg, repr(arg)) for karg, arg in kwargs])
|
||||
sargs = ','.join([str(arg) for arg in args[1:]]) + '' if not skwargs else (',' + str(skwargs))
|
||||
|
||||
call = "%s(%s)" % (fu.__name__, sargs)
|
||||
@ -61,13 +67,15 @@ def debug_log(enable = True):
|
||||
res = fu(*args, **kwargs)
|
||||
log.debug("%s -> %s", call, repr(res))
|
||||
return res
|
||||
|
||||
return new_fu if enable else fu
|
||||
|
||||
return new_dec
|
||||
|
||||
|
||||
|
||||
debug_me = True
|
||||
|
||||
|
||||
class MumoManagerRemote(object):
|
||||
"""
|
||||
Manager object handed to MumoModules. This module
|
||||
@ -76,7 +84,7 @@ class MumoManagerRemote(object):
|
||||
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):
|
||||
self.__master = master
|
||||
@ -88,7 +96,7 @@ class MumoManagerRemote(object):
|
||||
def getQueue(self):
|
||||
return self.__queue
|
||||
|
||||
def subscribeMetaCallbacks(self, handler, servers = SERVERS_ALL):
|
||||
def subscribeMetaCallbacks(self, handler, servers=SERVERS_ALL):
|
||||
"""
|
||||
Subscribe to meta callbacks. Subscribes the given handler to the following
|
||||
callbacks:
|
||||
@ -102,7 +110,7 @@ class MumoManagerRemote(object):
|
||||
"""
|
||||
return self.__master.subscribeMetaCallbacks(self.__queue, handler, servers)
|
||||
|
||||
def unsubscribeMetaCallbacks(self, handler, servers = SERVERS_ALL):
|
||||
def unsubscribeMetaCallbacks(self, handler, servers=SERVERS_ALL):
|
||||
"""
|
||||
Unsubscribe from meta callbacks. Unsubscribes the given handler from callbacks
|
||||
for the given servers.
|
||||
@ -113,7 +121,7 @@ class MumoManagerRemote(object):
|
||||
"""
|
||||
return self.__master.unscubscribeMetaCallbacks(self.__queue, handler, servers)
|
||||
|
||||
def subscribeServerCallbacks(self, handler, servers = SERVERS_ALL):
|
||||
def subscribeServerCallbacks(self, handler, servers=SERVERS_ALL):
|
||||
"""
|
||||
Subscribe to server callbacks. Subscribes the given handler to the following
|
||||
callbacks:
|
||||
@ -131,7 +139,7 @@ class MumoManagerRemote(object):
|
||||
"""
|
||||
return self.__master.subscribeServerCallbacks(self.__queue, handler, servers)
|
||||
|
||||
def unsubscribeServerCallbacks(self, handler, servers = SERVERS_ALL):
|
||||
def unsubscribeServerCallbacks(self, handler, servers=SERVERS_ALL):
|
||||
"""
|
||||
Unsubscribe from server callbacks. Unsubscribes the given handler from callbacks
|
||||
for the given servers.
|
||||
@ -171,7 +179,8 @@ class MumoManagerRemote(object):
|
||||
@param action: Action identifier passed to your callback (see above)
|
||||
@param text: Text for the menu entry
|
||||
@param handler: Handler function to call when the menu item is used
|
||||
@param context: Contexts to show entry in (can be a combination of ContextServer, ContextChannel and ContextUser)
|
||||
@param context: Contexts to show entry in (can be a combination of ContextServer, ContextChannel and
|
||||
ContextUser)
|
||||
"""
|
||||
|
||||
server_actions = self.__context_callbacks.get(server.id())
|
||||
@ -240,22 +249,22 @@ class MumoManagerRemote(object):
|
||||
class MumoManager(Worker):
|
||||
MAGIC_ALL = -1
|
||||
|
||||
cfg_default = {'modules':(('mod_dir', str, "modules/"),
|
||||
('cfg_dir', str, "modules-enabled/"),
|
||||
('timeout', int, 2))}
|
||||
cfg_default = {'modules': (('mod_dir', str, "modules/"),
|
||||
('cfg_dir', str, "modules-enabled/"),
|
||||
('timeout', int, 2))}
|
||||
|
||||
def __init__(self, murmur, context_callback_type, cfg = Config(default = cfg_default)):
|
||||
def __init__(self, murmur, context_callback_type, cfg=Config(default=cfg_default)):
|
||||
Worker.__init__(self, "MumoManager")
|
||||
self.queues = {} # {queue:module}
|
||||
self.modules = {} # {name:module}
|
||||
self.imports = {} # {name:import}
|
||||
self.queues = {} # {queue:module}
|
||||
self.modules = {} # {name:module}
|
||||
self.imports = {} # {name:import}
|
||||
self.cfg = cfg
|
||||
|
||||
self.murmur = murmur
|
||||
self.meta = None
|
||||
self.client_adapter = None
|
||||
|
||||
self.metaCallbacks = {} # {sid:{queue:[handler]}}
|
||||
self.metaCallbacks = {} # {sid:{queue:[handler]}}
|
||||
self.serverCallbacks = {}
|
||||
|
||||
self.context_callback_type = context_callback_type
|
||||
@ -279,13 +288,13 @@ class MumoManager(Worker):
|
||||
else:
|
||||
mdict[server][queue] = [handler]
|
||||
else:
|
||||
mdict[server] = {queue:[handler]}
|
||||
mdict[server] = {queue: [handler]}
|
||||
|
||||
def __rem_from_dict(self, mdict, queue, handler, servers):
|
||||
for server in servers:
|
||||
try:
|
||||
mdict[server][queue].remove(handler)
|
||||
except KeyError, ValueError:
|
||||
except KeyError as ValueError:
|
||||
pass
|
||||
|
||||
def __announce_to_dict(self, mdict, server, function, *args, **kwargs):
|
||||
@ -302,13 +311,13 @@ class MumoManager(Worker):
|
||||
|
||||
# Announce to all handlers of the given serverlist
|
||||
if server == self.MAGIC_ALL:
|
||||
servers = mdict.iterkeys()
|
||||
servers = iter(mdict.keys())
|
||||
else:
|
||||
servers = [self.MAGIC_ALL, server]
|
||||
|
||||
for server in servers:
|
||||
try:
|
||||
for queue, handlers in mdict[server].iteritems():
|
||||
for queue, handlers in mdict[server].items():
|
||||
for handler in handlers:
|
||||
self.__call_remote(queue, handler, function, *args, **kwargs)
|
||||
except KeyError:
|
||||
@ -317,31 +326,31 @@ class MumoManager(Worker):
|
||||
|
||||
def __call_remote(self, queue, handler, function, *args, **kwargs):
|
||||
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))
|
||||
except AttributeError, e:
|
||||
except AttributeError as e:
|
||||
mod = self.queues.get(queue, None)
|
||||
myname = ""
|
||||
for name, mymod in self.modules.iteritems():
|
||||
for name, mymod in self.modules.items():
|
||||
if mod == mymod:
|
||||
myname = name
|
||||
if myname:
|
||||
self.log().error("Handler class registered by module '%s' does not handle function '%s'. Call failed.", myname, function)
|
||||
self.log().error("Handler class registered by module '%s' does not handle function '%s'. Call failed.",
|
||||
myname, function)
|
||||
else:
|
||||
self.log().exception(e)
|
||||
|
||||
|
||||
#
|
||||
#-- Module multiplexing functionality
|
||||
# -- Module multiplexing functionality
|
||||
#
|
||||
|
||||
@local_thread
|
||||
def announceConnected(self, meta = None):
|
||||
def announceConnected(self, meta=None):
|
||||
"""
|
||||
Call connected handler on all handlers
|
||||
"""
|
||||
self.meta = meta
|
||||
for queue, module in self.queues.iteritems():
|
||||
for queue, module in self.queues.items():
|
||||
self.__call_remote(queue, module, "connected")
|
||||
|
||||
@local_thread
|
||||
@ -349,7 +358,7 @@ class MumoManager(Worker):
|
||||
"""
|
||||
Call disconnected handler on all handlers
|
||||
"""
|
||||
for queue, module in self.queues.iteritems():
|
||||
for queue, module in self.queues.items():
|
||||
self.__call_remote(queue, module, "disconnected")
|
||||
|
||||
@local_thread
|
||||
@ -377,7 +386,7 @@ class MumoManager(Worker):
|
||||
self.__announce_to_dict(self.serverCallbacks, server, function, *args, **kwargs)
|
||||
|
||||
#
|
||||
#--- Module self management functionality
|
||||
# --- Module self management functionality
|
||||
#
|
||||
|
||||
@local_thread
|
||||
@ -439,11 +448,11 @@ class MumoManager(Worker):
|
||||
"""
|
||||
return self.meta
|
||||
|
||||
#--- Module load/start/stop/unload functionality
|
||||
# --- Module load/start/stop/unload functionality
|
||||
#
|
||||
@local_thread_blocking
|
||||
@debug_log(debug_me)
|
||||
def loadModules(self, names = None):
|
||||
def loadModules(self, names=None):
|
||||
"""
|
||||
Loads a list of modules from the mumo directory structure by name.
|
||||
|
||||
@ -469,30 +478,30 @@ class MumoManager(Worker):
|
||||
for name in names:
|
||||
try:
|
||||
modinst = self._loadModule_noblock(name)
|
||||
loadedmodules[name] = modinst
|
||||
loadedmodules[name] = modinst
|
||||
except FailedLoadModuleException:
|
||||
pass
|
||||
|
||||
return loadedmodules
|
||||
|
||||
@local_thread_blocking
|
||||
def loadModuleCls(self, name, modcls, module_cfg = None):
|
||||
def loadModuleCls(self, name, modcls, module_cfg=None):
|
||||
return self._loadModuleCls_noblock(name, modcls, module_cfg)
|
||||
|
||||
@debug_log(debug_me)
|
||||
def _loadModuleCls_noblock(self, name, modcls, module_cfg = None):
|
||||
def _loadModuleCls_noblock(self, name, modcls, module_cfg=None):
|
||||
log = self.log()
|
||||
|
||||
if name in self.modules:
|
||||
log.error("Module '%s' already loaded", name)
|
||||
return
|
||||
|
||||
modqueue = Queue.Queue()
|
||||
modqueue = queue.Queue()
|
||||
modmanager = MumoManagerRemote(self, name, modqueue)
|
||||
|
||||
try:
|
||||
modinst = modcls(name, modmanager, module_cfg)
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
msg = "Module '%s' failed to initialize" % name
|
||||
log.error(msg)
|
||||
log.exception(e)
|
||||
@ -543,14 +552,14 @@ class MumoManager(Worker):
|
||||
try:
|
||||
mod = __import__(name)
|
||||
self.imports[name] = mod
|
||||
except ImportError, e:
|
||||
except ImportError as e:
|
||||
msg = "Failed to import module '%s', reason: %s" % (name, str(e))
|
||||
log.error(msg)
|
||||
raise FailedLoadModuleImportException(msg)
|
||||
|
||||
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")
|
||||
except AttributeError:
|
||||
modcls = getattr(mod, name)
|
||||
@ -563,7 +572,7 @@ class MumoManager(Worker):
|
||||
|
||||
@local_thread_blocking
|
||||
@debug_log(debug_me)
|
||||
def startModules(self, names = None):
|
||||
def startModules(self, names=None):
|
||||
"""
|
||||
Start a module by name
|
||||
|
||||
@ -575,12 +584,12 @@ class MumoManager(Worker):
|
||||
|
||||
if not names:
|
||||
# If no names are given start all models
|
||||
names = self.modules.iterkeys()
|
||||
names = iter(self.modules.keys())
|
||||
|
||||
for name in names:
|
||||
try:
|
||||
modinst = self.modules[name]
|
||||
if not modinst.isAlive():
|
||||
if not modinst.is_alive():
|
||||
modinst.start()
|
||||
log.debug("Module '%s' started", name)
|
||||
else:
|
||||
@ -593,7 +602,7 @@ class MumoManager(Worker):
|
||||
|
||||
@local_thread_blocking
|
||||
@debug_log(debug_me)
|
||||
def stopModules(self, names = None, force = False):
|
||||
def stopModules(self, names=None, force=False):
|
||||
"""
|
||||
Stop a list of modules by name. Note that this only works
|
||||
for well behaved modules. At this point if a module is really going
|
||||
@ -608,7 +617,7 @@ class MumoManager(Worker):
|
||||
|
||||
if not names:
|
||||
# If no names are given start all models
|
||||
names = self.modules.iterkeys()
|
||||
names = iter(self.modules.keys())
|
||||
|
||||
for name in names:
|
||||
try:
|
||||
@ -620,29 +629,29 @@ class MumoManager(Worker):
|
||||
|
||||
if force:
|
||||
# We will have to drain the modules queues
|
||||
for queue, module in self.queues.iteritems():
|
||||
for queue, module in self.queues.items():
|
||||
if module in self.modules:
|
||||
try:
|
||||
while queue.get_nowait(): pass
|
||||
except Queue.Empty: pass
|
||||
except queue.Empty:
|
||||
pass
|
||||
|
||||
for modinst in stoppedmodules.itervalues():
|
||||
if modinst.isAlive():
|
||||
for modinst in stoppedmodules.values():
|
||||
if modinst.is_alive():
|
||||
modinst.stop()
|
||||
log.debug("Module '%s' is being stopped", name)
|
||||
else:
|
||||
log.debug("Module '%s' already stopped", name)
|
||||
|
||||
for modinst in stoppedmodules.itervalues():
|
||||
modinst.join(timeout = self.cfg.modules.timeout)
|
||||
for modinst in stoppedmodules.values():
|
||||
modinst.join(timeout=self.cfg.modules.timeout)
|
||||
|
||||
return stoppedmodules
|
||||
|
||||
def stop(self, force = True):
|
||||
def stop(self, force=True):
|
||||
"""
|
||||
Stops all modules and shuts down the manager.
|
||||
"""
|
||||
self.log().debug("Stopping")
|
||||
self.stopModules()
|
||||
Worker.stop(self, force)
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -30,155 +30,154 @@
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import unittest
|
||||
import Queue
|
||||
from mumo_manager import MumoManager, MumoManagerRemote
|
||||
from mumo_module import MumoModule
|
||||
from logging import basicConfig, ERROR, getLogger
|
||||
import logging
|
||||
from logging import getLogger
|
||||
from threading import Event
|
||||
|
||||
from mumo_manager import MumoManager
|
||||
from mumo_module import MumoModule
|
||||
|
||||
|
||||
class MumoManagerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
l = getLogger("MumoManager")
|
||||
l.disabled = True
|
||||
|
||||
|
||||
class MyModule(MumoModule):
|
||||
def __init__(self, name, manager, configuration = None):
|
||||
def __init__(self, name, manager, configuration=None):
|
||||
MumoModule.__init__(self, name, manager, configuration)
|
||||
|
||||
|
||||
self.estarted = Event()
|
||||
self.estopped = Event()
|
||||
self.econnected = Event()
|
||||
self.edisconnected = Event()
|
||||
|
||||
|
||||
self.emeta = Event()
|
||||
self.econtext = Event()
|
||||
self.eserver = Event()
|
||||
|
||||
|
||||
def onStart(self):
|
||||
self.estarted.set()
|
||||
|
||||
|
||||
def onStop(self):
|
||||
self.estopped.set()
|
||||
|
||||
|
||||
def connected(self):
|
||||
man = self.manager()
|
||||
man.subscribeMetaCallbacks(self)
|
||||
man.subscribeServerCallbacks(self)
|
||||
man.subscribeContextCallbacks(self)
|
||||
self.econnected.set()
|
||||
|
||||
|
||||
def disconnected(self):
|
||||
self.edisconnected.set()
|
||||
|
||||
|
||||
def metaCallMe(self, arg1, arg2):
|
||||
if arg1 == "arg1" and arg2 == "arg2":
|
||||
self.emeta.set()
|
||||
|
||||
|
||||
def contextCallMe(self, server, arg1, arg2):
|
||||
if arg1 == "arg1" and arg2 == "arg2":
|
||||
self.econtext.set()
|
||||
|
||||
|
||||
def serverCallMe(self, server, arg1, arg2):
|
||||
if arg1 == "arg1" and arg2 == "arg2":
|
||||
self.eserver.set()
|
||||
|
||||
|
||||
self.mymod = MyModule
|
||||
|
||||
class conf(object):
|
||||
pass # Dummy class
|
||||
|
||||
pass # Dummy class
|
||||
|
||||
self.cfg = conf()
|
||||
self.cfg.test = 10
|
||||
|
||||
#
|
||||
#--- Helpers for independent test env creation
|
||||
# --- Helpers for independent test env creation
|
||||
#
|
||||
def up(self):
|
||||
man = MumoManager(None)
|
||||
man = MumoManager(None, None)
|
||||
man.start()
|
||||
|
||||
mod = man.loadModuleCls("MyModule", self.mymod, self.cfg)
|
||||
man.startModules()
|
||||
|
||||
return (man, mod)
|
||||
|
||||
|
||||
return man, mod
|
||||
|
||||
def down(self, man, mod):
|
||||
man.stopModules()
|
||||
man.stop()
|
||||
man.join(timeout=1)
|
||||
|
||||
|
||||
#
|
||||
#--- Tests
|
||||
# --- Tests
|
||||
#
|
||||
def testModuleStarted(self):
|
||||
man, mod = self.up()
|
||||
|
||||
|
||||
mod.estarted.wait(timeout=1)
|
||||
assert(mod.estarted.is_set())
|
||||
|
||||
assert (mod.estarted.is_set())
|
||||
|
||||
self.down(man, mod)
|
||||
|
||||
|
||||
def testModuleStopStart(self):
|
||||
man ,mod = self.up()
|
||||
|
||||
man, mod = self.up()
|
||||
|
||||
tos = ["MyModule"]
|
||||
self.assertEquals(list(man.stopModules(tos).iterkeys()), tos)
|
||||
self.assertEqual(list(man.stopModules(tos).keys()), tos)
|
||||
mod.estopped.wait(timeout=1)
|
||||
assert(mod.estopped.is_set())
|
||||
|
||||
assert (mod.estopped.is_set())
|
||||
|
||||
self.down(man, mod)
|
||||
|
||||
def testModuleConnectAndDisconnect(self):
|
||||
man, mod = self.up()
|
||||
|
||||
|
||||
man.announceConnected()
|
||||
mod.econnected.wait(timeout=1)
|
||||
assert(mod.econnected.is_set())
|
||||
assert (mod.econnected.is_set())
|
||||
man.announceDisconnected()
|
||||
mod.edisconnected.wait(timeout=1)
|
||||
assert(mod.edisconnected.is_set())
|
||||
|
||||
assert (mod.edisconnected.is_set())
|
||||
|
||||
self.down(man, mod)
|
||||
|
||||
|
||||
def testMetaCallback(self):
|
||||
man, mod = self.up()
|
||||
man.announceConnected()
|
||||
mod.econnected.wait(timeout=1)
|
||||
assert(mod.econnected.is_set())
|
||||
man.announceMeta(man.MAGIC_ALL, "metaCallMe", "arg1", arg2 = "arg2")
|
||||
assert (mod.econnected.is_set())
|
||||
man.announceMeta(man.MAGIC_ALL, "metaCallMe", "arg1", arg2="arg2")
|
||||
mod.emeta.wait(timeout=1)
|
||||
assert(mod.emeta.is_set())
|
||||
assert (mod.emeta.is_set())
|
||||
man.announceDisconnected()
|
||||
self.down(man, mod)
|
||||
|
||||
def testContextCallback(self):
|
||||
man, mod = self.up()
|
||||
man.announceConnected()
|
||||
mod.econnected.wait(timeout=1)
|
||||
assert(mod.econnected.is_set())
|
||||
man.announceContext(man.MAGIC_ALL, "contextCallMe", "server", "arg1", arg2 = "arg2")
|
||||
mod.econtext.wait(timeout=1)
|
||||
assert(mod.econtext.is_set())
|
||||
man.announceDisconnected()
|
||||
self.down(man, mod)
|
||||
|
||||
|
||||
# FIXME: Test ContextCallbacks correctly
|
||||
# def testContextCallback(self):
|
||||
# man, mod = self.up()
|
||||
# man.announceConnected()
|
||||
# mod.econnected.wait(timeout=1)
|
||||
# assert (mod.econnected.is_set())
|
||||
# man.announceContext(man.MAGIC_ALL, "contextCallMe", "server", "arg1", arg2="arg2")
|
||||
# mod.econtext.wait(timeout=1)
|
||||
# assert (mod.econtext.is_set())
|
||||
# man.announceDisconnected()
|
||||
# self.down(man, mod)
|
||||
|
||||
def testServerCallback(self):
|
||||
man, mod = self.up()
|
||||
man.announceConnected()
|
||||
mod.econnected.wait(timeout=1)
|
||||
assert(mod.econnected.is_set())
|
||||
man.announceServer(man.MAGIC_ALL, "serverCallMe", "server", "arg1", arg2 = "arg2")
|
||||
assert (mod.econnected.is_set())
|
||||
man.announceServer(man.MAGIC_ALL, "serverCallMe", "server", "arg1", arg2="arg2")
|
||||
mod.eserver.wait(timeout=1)
|
||||
assert(mod.eserver.is_set())
|
||||
assert (mod.eserver.is_set())
|
||||
man.announceDisconnected()
|
||||
self.down(man, mod)
|
||||
|
||||
|
||||
def tearDown(self):
|
||||
pass
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
# import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,73 +29,70 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from config import (Config,
|
||||
x2bool,
|
||||
commaSeperatedIntegers,
|
||||
commaSeperatedStrings,
|
||||
commaSeperatedBool)
|
||||
from config import (Config)
|
||||
|
||||
from worker import Worker
|
||||
|
||||
|
||||
class MumoModule(Worker):
|
||||
default_config = {}
|
||||
|
||||
def __init__(self, name, manager, configuration = None):
|
||||
|
||||
def __init__(self, name, manager, configuration=None):
|
||||
Worker.__init__(self, name, manager.getQueue())
|
||||
self.__manager = manager
|
||||
|
||||
if isinstance(configuration, basestring):
|
||||
|
||||
if isinstance(configuration, str):
|
||||
# If we are passed a string expect a config file there
|
||||
if configuration:
|
||||
self.__cfg = Config(configuration, self.default_config)
|
||||
elif self.default_config:
|
||||
self.__cfg = Config(default = self.default_config)
|
||||
self.__cfg = Config(default=self.default_config)
|
||||
else:
|
||||
self.__cfg = None
|
||||
else:
|
||||
# If we aren't passed a string it will be a config object or None
|
||||
self.__cfg = configuration
|
||||
|
||||
|
||||
self.log().info("Initialized")
|
||||
|
||||
#--- Accessors
|
||||
# --- Accessors
|
||||
def manager(self):
|
||||
return self.__manager
|
||||
|
||||
|
||||
def cfg(self):
|
||||
return self.__cfg
|
||||
|
||||
#--- Module control
|
||||
|
||||
|
||||
|
||||
# --- Module control
|
||||
|
||||
def onStart(self):
|
||||
self.log().info("Start")
|
||||
|
||||
|
||||
def onStop(self):
|
||||
self.log().info("Stop")
|
||||
|
||||
#--- Events
|
||||
|
||||
|
||||
# --- Events
|
||||
|
||||
def connected(self):
|
||||
# Called once the Ice connection to the murmur server
|
||||
# is established.
|
||||
#
|
||||
# All event registration should happen here
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
def disconnected(self):
|
||||
# Called once a loss of Ice connectivity is detected.
|
||||
#
|
||||
|
||||
|
||||
pass
|
||||
|
||||
|
||||
|
||||
|
||||
def logModFu(fu):
|
||||
def new_fu(self, *args, **kwargs):
|
||||
log = self.log()
|
||||
argss = '' if len(args)==0 else ',' + ','.join(['"%s"' % str(arg) for arg in args])
|
||||
kwargss = '' if len(kwargs)==0 else ','.join('%s="%s"' % (kw, str(arg)) for kw, arg in kwargs.iteritems())
|
||||
argss = '' if len(args) == 0 else ',' + ','.join(['"%s"' % str(arg) for arg in args])
|
||||
kwargss = '' if len(kwargs) == 0 else ','.join('%s="%s"' % (kw, str(arg)) for kw, arg in kwargs.items())
|
||||
log.debug("%s(%s%s%s)", fu.__name__, str(self), argss, kwargss)
|
||||
return fu(self, *args, **kwargs)
|
||||
|
||||
return new_fu
|
||||
|
12
testsuite.py
12
testsuite.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -30,15 +30,7 @@
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
if __name__ == "__main__":
|
||||
import unittest
|
||||
|
||||
from worker_test import *
|
||||
from config_test import *
|
||||
from mumo_manager_test import *
|
||||
|
||||
from modules.source.source_test import *
|
||||
from modules.source.users_test import *
|
||||
from modules.source.db_test import *
|
||||
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
# import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
|
@ -1,2 +1,2 @@
|
||||
# No real module, just here to keep pydev and its
|
||||
# test runner happy.
|
||||
# test runner happy.
|
||||
|
286
tools/mbf2man.py
286
tools/mbf2man.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -39,251 +39,249 @@ import sys
|
||||
import tempfile
|
||||
from optparse import OptionParser
|
||||
|
||||
# Default settings
|
||||
|
||||
|
||||
import Ice
|
||||
import IcePy
|
||||
|
||||
# Default settings
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = OptionParser()
|
||||
parser.add_option('-t', '--target',
|
||||
help = 'Host to connect to', default = "127.0.0.1")
|
||||
help='Host to connect to', default="127.0.0.1")
|
||||
parser.add_option('-p', '--port',
|
||||
help = 'Port to connect to', default = "6502")
|
||||
help='Port to connect to', default="6502")
|
||||
parser.add_option('-b', '--base',
|
||||
help = 'Channel id of the base channel', default = '0')
|
||||
help='Channel id of the base channel', default='0')
|
||||
parser.add_option('-v', '--vserver',
|
||||
help = 'Virtual server id', default = '1')
|
||||
help='Virtual server id', default='1')
|
||||
parser.add_option('-i', '--ice',
|
||||
help = 'Path to slice file', default = 'Murmur.ice')
|
||||
help='Path to slice file', default='Murmur.ice')
|
||||
parser.add_option('-s', '--secret',
|
||||
help = 'Ice secret', default = '')
|
||||
parser.add_option('-l', '--linkteams', action = 'store_true',
|
||||
help = 'Link teams so opposing players can hear each other', default = False)
|
||||
help='Ice secret', default='')
|
||||
parser.add_option('-l', '--linkteams', action='store_true',
|
||||
help='Link teams so opposing players can hear each other', default=False)
|
||||
parser.add_option('-n', '--name',
|
||||
help = 'Treename', default = 'BF2')
|
||||
parser.add_option('-o', '--out', default = 'bf2.ini',
|
||||
help = 'File to output configuration to')
|
||||
help='Treename', default='BF2')
|
||||
parser.add_option('-o', '--out', default='bf2.ini',
|
||||
help='File to output configuration to')
|
||||
parser.add_option('-d', '--slicedir',
|
||||
help = 'System slice directory used when getSliceDir is not available', default = '/usr/share/slice')
|
||||
help='System slice directory used when getSliceDir is not available', default='/usr/share/slice')
|
||||
(option, args) = parser.parse_args()
|
||||
|
||||
|
||||
host = option.target
|
||||
slicedir = option.slicedir
|
||||
try:
|
||||
port = int(option.port)
|
||||
except ValueError:
|
||||
print "Port value '%s' is invalid" % option.port
|
||||
print("Port value '%s' is invalid" % option.port)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
try:
|
||||
basechan = int(option.base)
|
||||
if basechan < 0: raise ValueError
|
||||
except ValueError:
|
||||
print "Base channel value '%s' invalid" % option.base
|
||||
print("Base channel value '%s' invalid" % option.base)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
try:
|
||||
sid = int(option.vserver)
|
||||
if sid < 1: raise ValueError
|
||||
except ValueError:
|
||||
print "Virtual server id value '%s' invalid" % option.vserver
|
||||
print("Virtual server id value '%s' invalid" % option.vserver)
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
name = option.name
|
||||
|
||||
|
||||
prxstr = "Meta:tcp -h %s -p %d -t 1000" % (host, port)
|
||||
secret = option.secret
|
||||
|
||||
|
||||
props = Ice.createProperties(sys.argv)
|
||||
props.setProperty("Ice.ImplicitContext", "Shared")
|
||||
idata = Ice.InitializationData()
|
||||
idata.properties = props
|
||||
|
||||
|
||||
ice = Ice.initialize(idata)
|
||||
prx = ice.stringToProxy(prxstr)
|
||||
print "Done"
|
||||
print("Done")
|
||||
|
||||
|
||||
def lslice(slf):
|
||||
if not hasattr(Ice, "getSliceDir"):
|
||||
Ice.loadSlice('-I%s %s' % (slicedir, slf))
|
||||
else:
|
||||
Ice.loadSlice('', ['-I' + Ice.getSliceDir(), slf])
|
||||
|
||||
|
||||
|
||||
try:
|
||||
print "Trying to retrieve slice dynamically from server...",
|
||||
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, (), (), (), IcePy._t_string, ())
|
||||
print("Trying to retrieve slice dynamically from server...", end=' ')
|
||||
op = IcePy.Operation('getSlice', Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, True, (), (), (),
|
||||
IcePy._t_string, ())
|
||||
if hasattr(Ice, "getSliceDir"):
|
||||
slice = op.invoke(prx, ((), None))
|
||||
else:
|
||||
slice = op.invoke(prx, (), None)
|
||||
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix = '.ice')
|
||||
(dynslicefiledesc, dynslicefilepath) = tempfile.mkstemp(suffix='.ice')
|
||||
dynslicefile = os.fdopen(dynslicefiledesc, 'w')
|
||||
dynslicefile.write(slice)
|
||||
dynslicefile.flush()
|
||||
lslice(dynslicefilepath)
|
||||
dynslicefile.close()
|
||||
os.remove(dynslicefilepath)
|
||||
print "Success"
|
||||
except Exception, e:
|
||||
print "Failed"
|
||||
print str(e)
|
||||
print("Success")
|
||||
except Exception as e:
|
||||
print("Failed")
|
||||
print(str(e))
|
||||
slicefile = option.ice
|
||||
print "Load slice (%s)..." % slicefile,
|
||||
print("Load slice (%s)..." % slicefile, end=' ')
|
||||
lslice(slicefile)
|
||||
print "Done"
|
||||
|
||||
print "Import dynamically compiled murmur class...",
|
||||
print("Done")
|
||||
|
||||
print("Import dynamically compiled murmur class...", end=' ')
|
||||
import Murmur
|
||||
print "Done"
|
||||
print "Establish ice connection...",
|
||||
|
||||
|
||||
print("Done")
|
||||
print("Establish ice connection...", end=' ')
|
||||
|
||||
if secret:
|
||||
print "[protected]...",
|
||||
print("[protected]...", end=' ')
|
||||
ice.getImplicitContext().put("secret", secret)
|
||||
|
||||
|
||||
murmur = Murmur.MetaPrx.checkedCast(prx)
|
||||
print "Done"
|
||||
|
||||
print "Get server...",
|
||||
print("Done")
|
||||
|
||||
print("Get server...", end=' ')
|
||||
server = murmur.getServer(sid)
|
||||
print "Done (%d)" % sid
|
||||
|
||||
ini = {}
|
||||
ini['mumble_server'] = sid
|
||||
ini['name'] = name
|
||||
ini['ipport_filter'] = '.*'
|
||||
|
||||
print "Creating channel structure:"
|
||||
print("Done (%d)" % sid)
|
||||
|
||||
ini = {'mumble_server': sid, 'name': name, 'ipport_filter': '.*'}
|
||||
|
||||
print("Creating channel structure:")
|
||||
ACL = Murmur.ACL
|
||||
EAT = Murmur.PermissionEnter | Murmur.PermissionTraverse
|
||||
W = Murmur.PermissionWhisper
|
||||
S = Murmur.PermissionSpeak
|
||||
print name
|
||||
print(name)
|
||||
ini['left'] = basechan
|
||||
gamechan = server.addChannel(name, basechan)
|
||||
|
||||
|
||||
# Relevant function signatures
|
||||
# Murmur.ACL(self, applyHere=False, applySubs=False,
|
||||
# inherited=False, userid=0, group='', allow=0, deny=0)
|
||||
|
||||
|
||||
# server.setACL(self, channelid, acls, groups, inherit, _ctx=None)
|
||||
#
|
||||
server.setACL(gamechan,
|
||||
[ACL(applyHere = True,
|
||||
applySubs = True,
|
||||
userid = -1,
|
||||
group = 'all',
|
||||
deny = EAT | W | S),
|
||||
ACL(applyHere = True,
|
||||
applySubs = True,
|
||||
userid = -1,
|
||||
group = '~bf2_%s_game' % name,
|
||||
allow = S),
|
||||
ACL(applyHere = True,
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = '~bf2_%s_game' % name,
|
||||
allow = EAT | W)],
|
||||
[], True)
|
||||
|
||||
[ACL(applyHere=True,
|
||||
applySubs=True,
|
||||
userid=-1,
|
||||
group='all',
|
||||
deny=EAT | W | S),
|
||||
ACL(applyHere=True,
|
||||
applySubs=True,
|
||||
userid=-1,
|
||||
group='~bf2_%s_game' % name,
|
||||
allow=S),
|
||||
ACL(applyHere=True,
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group='~bf2_%s_game' % name,
|
||||
allow=EAT | W)],
|
||||
[], True)
|
||||
|
||||
gamechanstate = server.getChannelState(gamechan)
|
||||
|
||||
teams = {
|
||||
"opfor": "Team 1",
|
||||
"blufor": "Team 2"
|
||||
teams = {
|
||||
"opfor": "Team 1",
|
||||
"blufor": "Team 2"
|
||||
}
|
||||
id_to_squad_name = {
|
||||
"no": "No Squad",
|
||||
"first": "Squad 1",
|
||||
"second": "Squad 2",
|
||||
"third": "Squad 3",
|
||||
"fourth": "Squad 4",
|
||||
"fifth": "Squad 5",
|
||||
"sixth": "Squad 6",
|
||||
"seventh": "Squad 7",
|
||||
"eighth": "Squad 8",
|
||||
"ninth": "Squad 9"
|
||||
id_to_squad_name = {
|
||||
"no": "No Squad",
|
||||
"first": "Squad 1",
|
||||
"second": "Squad 2",
|
||||
"third": "Squad 3",
|
||||
"fourth": "Squad 4",
|
||||
"fifth": "Squad 5",
|
||||
"sixth": "Squad 6",
|
||||
"seventh": "Squad 7",
|
||||
"eighth": "Squad 8",
|
||||
"ninth": "Squad 9"
|
||||
}
|
||||
for team,team_name in teams.items():
|
||||
print name + "/" + team_name
|
||||
for team, team_name in list(teams.items()):
|
||||
print(name + "/" + team_name)
|
||||
cid = server.addChannel(team_name, gamechan)
|
||||
teamchanstate = server.getChannelState(cid)
|
||||
if option.linkteams:
|
||||
gamechanstate.links.append(cid)
|
||||
|
||||
ini[team] = cid
|
||||
|
||||
|
||||
server.setACL(ini[team],
|
||||
[ACL(applyHere = True,
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = '~bf2_team',
|
||||
allow = EAT | W)],
|
||||
[ACL(applyHere=True,
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group='~bf2_team',
|
||||
allow=EAT | W)],
|
||||
[], True)
|
||||
|
||||
print name + "/" + team_name + "/Commander"
|
||||
|
||||
print(name + "/" + team_name + "/Commander")
|
||||
cid = server.addChannel("Commander", ini[team])
|
||||
teamchanstate.links.append(cid)
|
||||
ini[team + "_commander"] = cid
|
||||
|
||||
ini[team + "_commander"] = cid
|
||||
|
||||
server.setACL(ini[team + "_commander"],
|
||||
[ACL(applyHere = True,
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = '~bf2_commander',
|
||||
allow = EAT | W),
|
||||
ACL(applyHere = True,
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = '~bf2_squad_leader',
|
||||
allow = W)],
|
||||
[ACL(applyHere=True,
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group='~bf2_commander',
|
||||
allow=EAT | W),
|
||||
ACL(applyHere=True,
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group='~bf2_squad_leader',
|
||||
allow=W)],
|
||||
[], True)
|
||||
|
||||
state = server.getChannelState(ini[team+"_commander"])
|
||||
|
||||
state = server.getChannelState(ini[team + "_commander"])
|
||||
state.position = -1
|
||||
server.setChannelState(state)
|
||||
|
||||
for squad,squad_name in id_to_squad_name.items():
|
||||
print name + "/" + team_name + "/" + squad_name
|
||||
|
||||
for squad, squad_name in list(id_to_squad_name.items()):
|
||||
print(name + "/" + team_name + "/" + squad_name)
|
||||
cid = server.addChannel(squad_name, ini[team])
|
||||
teamchanstate.links.append(cid)
|
||||
ini[team + "_" + squad + "_squad"] = cid
|
||||
|
||||
ini[team + "_" + squad + "_squad"] = cid
|
||||
|
||||
ini[team + "_" + squad + "_squad_leader"] = ini[team + "_" + squad + "_squad"]
|
||||
server.setACL(ini[team + "_" + squad + "_squad"],
|
||||
[ACL(applyHere = True,
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = '~bf2_%s_squad' % squad,
|
||||
allow = EAT | W),
|
||||
ACL(applyHere = True,
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = '~bf2_commander',
|
||||
allow = EAT | W),
|
||||
ACL(applyHere = True,
|
||||
applySubs = False,
|
||||
userid = -1,
|
||||
group = '~bf2_squad_leader',
|
||||
allow = W)],
|
||||
[ACL(applyHere=True,
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group='~bf2_%s_squad' % squad,
|
||||
allow=EAT | W),
|
||||
ACL(applyHere=True,
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group='~bf2_commander',
|
||||
allow=EAT | W),
|
||||
ACL(applyHere=True,
|
||||
applySubs=False,
|
||||
userid=-1,
|
||||
group='~bf2_squad_leader',
|
||||
allow=W)],
|
||||
[], True)
|
||||
server.setChannelState(teamchanstate)
|
||||
server.setChannelState(gamechanstate)
|
||||
print "Channel structure created"
|
||||
|
||||
print "Writing configuration to output file '%s'..." % option.out,
|
||||
print("Channel structure created")
|
||||
|
||||
print("Writing configuration to output file '%s'..." % option.out, end=' ')
|
||||
f = open(option.out, "w")
|
||||
print>>f, "; Configuration created by mbf2man\n"
|
||||
print>>f, "[bf2]\ngamecount = 1\n"
|
||||
print>>f, "[g0]"
|
||||
|
||||
print("; Configuration created by mbf2man\n", file=f)
|
||||
print("[bf2]\ngamecount = 1\n", file=f)
|
||||
print("[g0]", file=f)
|
||||
|
||||
for key in sorted(ini):
|
||||
value = ini[key]
|
||||
print>>f, "%s = %s" % (key, value)
|
||||
|
||||
f.close()
|
||||
print "Done"
|
||||
print("%s = %s" % (key, value), file=f)
|
||||
|
||||
f.close()
|
||||
print("Done")
|
||||
|
64
worker.py
64
worker.py
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,21 +29,25 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
from threading import Thread
|
||||
from Queue import Queue, Empty
|
||||
from logging import getLogger
|
||||
from queue import Queue, Empty
|
||||
from threading import Thread
|
||||
|
||||
|
||||
def local_thread(fu):
|
||||
"""
|
||||
Decorator which makes a function execute in the local worker thread
|
||||
Return values are discarded
|
||||
"""
|
||||
|
||||
def new_fu(*args, **kwargs):
|
||||
self = args[0]
|
||||
self.message_queue().put((None, fu, args, kwargs))
|
||||
|
||||
return new_fu
|
||||
|
||||
def local_thread_blocking(fu, timeout = None):
|
||||
|
||||
def local_thread_blocking(fu, timeout=None):
|
||||
"""
|
||||
Decorator which makes a function execute in the local worker thread
|
||||
The function will block until return values are available or timeout
|
||||
@ -51,96 +55,98 @@ def local_thread_blocking(fu, timeout = None):
|
||||
|
||||
@param timeout Timeout in seconds
|
||||
"""
|
||||
|
||||
def new_fu(*args, **kwargs):
|
||||
self = args[0]
|
||||
out = Queue()
|
||||
self.message_queue().put((out, fu, args, kwargs))
|
||||
ret, ex = out.get(True, timeout)
|
||||
ret, ex = out.get(True, timeout)
|
||||
if ex:
|
||||
raise ex
|
||||
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
return new_fu
|
||||
|
||||
|
||||
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.
|
||||
|
||||
@param name Name of the thread to run the worker in
|
||||
@param message_queue Message queue on which to receive commands
|
||||
"""
|
||||
|
||||
Thread.__init__(self, name = name)
|
||||
|
||||
Thread.__init__(self, name=name)
|
||||
self.daemon = True
|
||||
self.__in = message_queue if message_queue != None else Queue()
|
||||
self.__log = getLogger(name)
|
||||
self.__name = name
|
||||
|
||||
#--- Accessors
|
||||
|
||||
# --- Accessors
|
||||
def log(self):
|
||||
return self.__log
|
||||
|
||||
|
||||
def name(self):
|
||||
return self.__name
|
||||
|
||||
|
||||
def message_queue(self):
|
||||
return self.__in
|
||||
|
||||
#--- Overridable convience stuff
|
||||
# --- Overridable convience stuff
|
||||
def onStart(self):
|
||||
"""
|
||||
Override this function to perform actions on worker startup
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
def onStop(self):
|
||||
"""
|
||||
Override this function to perform actions on worker shutdown
|
||||
"""
|
||||
pass
|
||||
#--- Thread / Control
|
||||
|
||||
# --- Thread / Control
|
||||
def run(self):
|
||||
self.log().debug("Enter message loop")
|
||||
self.onStart()
|
||||
while True:
|
||||
msg = self.__in.get()
|
||||
if msg == None:
|
||||
if msg is None:
|
||||
break
|
||||
|
||||
|
||||
(out, fu, args, kwargs) = msg
|
||||
try:
|
||||
res = fu(*args, **kwargs)
|
||||
ex = None
|
||||
except Exception, e:
|
||||
except Exception as e:
|
||||
self.log().exception(e)
|
||||
res = None
|
||||
ex = e
|
||||
finally:
|
||||
if not out is None:
|
||||
if out is not None:
|
||||
out.put((res, ex))
|
||||
|
||||
|
||||
self.onStop()
|
||||
self.log().debug("Leave message loop")
|
||||
|
||||
def stop(self, force = True):
|
||||
|
||||
def stop(self, force=True):
|
||||
if force:
|
||||
try:
|
||||
while True:
|
||||
self.__in.get_nowait()
|
||||
except Empty:
|
||||
pass
|
||||
|
||||
|
||||
self.__in.put(None)
|
||||
|
||||
#--- Helpers
|
||||
|
||||
|
||||
# --- Helpers
|
||||
|
||||
@local_thread
|
||||
def call_by_name(self, handler, function_name, *args, **kwargs):
|
||||
return getattr(handler, function_name)(*args, **kwargs)
|
||||
|
||||
|
||||
@local_thread_blocking
|
||||
def call_by_name_blocking(self, handler, function_name, *args, **kwargs):
|
||||
return getattr(handler, function_name)(*args, **kwargs)
|
||||
|
@ -1,4 +1,4 @@
|
||||
#!/usr/bin/env python2
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8
|
||||
|
||||
# Copyright (C) 2010 Stefan Hacker <dd0t@users.sourceforge.net>
|
||||
@ -29,29 +29,28 @@
|
||||
# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
import unittest
|
||||
|
||||
import worker
|
||||
from worker import Worker, local_thread, local_thread_blocking
|
||||
from Queue import Queue
|
||||
from logging.handlers import BufferingHandler
|
||||
from logging import ERROR
|
||||
import logging
|
||||
|
||||
import unittest
|
||||
from logging import ERROR
|
||||
from logging.handlers import BufferingHandler
|
||||
from queue import Queue
|
||||
from threading import Event
|
||||
from time import sleep
|
||||
|
||||
|
||||
from worker import Worker, local_thread, local_thread_blocking
|
||||
|
||||
|
||||
class WorkerTest(unittest.TestCase):
|
||||
def setUp(self):
|
||||
|
||||
def set_ev(fu):
|
||||
def new_fu(*args, **kwargs):
|
||||
s = args[0]
|
||||
s.event.set()
|
||||
s.val = (args, kwargs)
|
||||
return fu(*args, **kwargs)
|
||||
|
||||
return new_fu
|
||||
|
||||
|
||||
class ATestWorker(Worker):
|
||||
def __init__(self, name, message_queue):
|
||||
Worker.__init__(self, name, message_queue)
|
||||
@ -59,64 +58,63 @@ class WorkerTest(unittest.TestCase):
|
||||
self.val = None
|
||||
self.started = False
|
||||
self.stopped = False
|
||||
|
||||
|
||||
@local_thread
|
||||
@set_ev
|
||||
def echo(self, val):
|
||||
return val
|
||||
|
||||
|
||||
@local_thread_blocking
|
||||
@set_ev
|
||||
def echo_block(self, val):
|
||||
return val
|
||||
|
||||
|
||||
def onStart(self):
|
||||
self.started = True
|
||||
|
||||
|
||||
def onStop(self):
|
||||
self.stopped = True
|
||||
|
||||
|
||||
@local_thread
|
||||
def raise_(self, ex):
|
||||
raise ex
|
||||
|
||||
|
||||
@local_thread_blocking
|
||||
def raise_blocking(self, ex):
|
||||
raise ex
|
||||
|
||||
|
||||
@set_ev
|
||||
def call_me_by_name(self, arg1, arg2):
|
||||
return
|
||||
|
||||
|
||||
def call_me_by_name_blocking(self, arg1, arg2):
|
||||
return arg1, arg2
|
||||
|
||||
|
||||
|
||||
self.buha = BufferingHandler(10000)
|
||||
|
||||
|
||||
q = Queue()
|
||||
self.q = q
|
||||
|
||||
|
||||
NAME = "Test"
|
||||
l = logging.getLogger(NAME)
|
||||
|
||||
|
||||
self.w = ATestWorker(NAME, q)
|
||||
self.assertEqual(self.w.log(), l)
|
||||
|
||||
|
||||
l.propagate = 0
|
||||
l.addHandler(self.buha)
|
||||
|
||||
|
||||
self.assertFalse(self.w.started)
|
||||
self.w.start()
|
||||
sleep(0.05)
|
||||
self.assertTrue(self.w.started)
|
||||
|
||||
def testName(self):
|
||||
assert(self.w.name() == "Test")
|
||||
|
||||
assert (self.w.name() == "Test")
|
||||
|
||||
def testMessageQueue(self):
|
||||
assert(self.w.message_queue() == self.q)
|
||||
|
||||
assert (self.w.message_queue() == self.q)
|
||||
|
||||
def testLocalThread(self):
|
||||
s = "Testing"
|
||||
self.w.event.clear()
|
||||
@ -124,46 +122,46 @@ class WorkerTest(unittest.TestCase):
|
||||
self.w.event.wait(5)
|
||||
args, kwargs = self.w.val
|
||||
|
||||
assert(args[1] == s)
|
||||
|
||||
assert (args[1] == s)
|
||||
|
||||
def testLocalThreadException(self):
|
||||
self.buha.flush()
|
||||
self.w.raise_(Exception())
|
||||
sleep(0.1) # hard delay
|
||||
assert(len(self.buha.buffer) != 0)
|
||||
assert(self.buha.buffer[0].levelno == ERROR)
|
||||
|
||||
sleep(0.1) # hard delay
|
||||
assert (len(self.buha.buffer) != 0)
|
||||
assert (self.buha.buffer[0].levelno == ERROR)
|
||||
|
||||
def testCallByName(self):
|
||||
self.w.event.clear()
|
||||
self.w.call_by_name(self.w, "call_me_by_name", "arg1", arg2="arg2")
|
||||
self.w.event.wait(5)
|
||||
args, kwargs = self.w.val
|
||||
|
||||
assert(args[1] == "arg1")
|
||||
assert(kwargs["arg2"] == "arg2")
|
||||
|
||||
|
||||
assert (args[1] == "arg1")
|
||||
assert (kwargs["arg2"] == "arg2")
|
||||
|
||||
def testLocalThreadBlocking(self):
|
||||
s = "Testing"
|
||||
assert(s == self.w.echo_block(s))
|
||||
|
||||
assert (s == self.w.echo_block(s))
|
||||
|
||||
def testLocalThreadExceptionBlocking(self):
|
||||
class TestException(Exception): pass
|
||||
|
||||
self.assertRaises(TestException, self.w.raise_blocking, TestException())
|
||||
|
||||
|
||||
def testCallByNameBlocking(self):
|
||||
arg1, arg2 = self.w.call_by_name_blocking(self.w, "call_me_by_name_blocking", "arg1", arg2="arg2")
|
||||
|
||||
assert(arg1 == "arg1")
|
||||
assert(arg2 == "arg2")
|
||||
assert (arg1 == "arg1")
|
||||
assert (arg2 == "arg2")
|
||||
|
||||
def tearDown(self):
|
||||
assert(self.w.stopped == False)
|
||||
assert (self.w.stopped is False)
|
||||
self.w.stop()
|
||||
self.w.join(5)
|
||||
assert(self.w.stopped == True)
|
||||
|
||||
assert self.w.stopped
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
#import sys;sys.argv = ['', 'Test.testName']
|
||||
# import sys;sys.argv = ['', 'Test.testName']
|
||||
unittest.main()
|
||||
|
Loading…
x
Reference in New Issue
Block a user