mirror of
https://github.com/nikdoof/test-auth.git
synced 2025-12-14 14:52:15 +00:00
Imported a cut down mumble app
This commit is contained in:
330
mumble/MumbleCtlDbus.py
Normal file
330
mumble/MumbleCtlDbus.py
Normal file
@@ -0,0 +1,330 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009, withgod <withgod@sourceforge.net>
|
||||
* 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
from PIL import Image
|
||||
from struct import pack, unpack
|
||||
from zlib import compress, decompress
|
||||
|
||||
from mctl import MumbleCtlBase
|
||||
from utils import ObjectInfo
|
||||
|
||||
import dbus
|
||||
from dbus.exceptions import DBusException
|
||||
|
||||
|
||||
def MumbleCtlDbus( connstring ):
|
||||
""" Choose the correct DBus handler (1.1.8 or legacy) to use. """
|
||||
|
||||
meta = dbus.Interface( dbus.SystemBus().get_object( connstring, '/' ), 'net.sourceforge.mumble.Meta' );
|
||||
|
||||
try:
|
||||
meta.getVersion();
|
||||
except DBusException:
|
||||
return MumbleCtlDbus_Legacy( connstring, meta );
|
||||
else:
|
||||
return MumbleCtlDbus_118( connstring, meta );
|
||||
|
||||
|
||||
class MumbleCtlDbus_118(MumbleCtlBase):
|
||||
method = "DBus";
|
||||
|
||||
def __init__( self, connstring, meta ):
|
||||
self.dbus_base = connstring;
|
||||
self.meta = meta;
|
||||
|
||||
def _getDbusMeta( self ):
|
||||
return self.meta;
|
||||
|
||||
def _getDbusServerObject( self, srvid):
|
||||
if srvid not in self.getBootedServers():
|
||||
raise SystemError, 'No murmur process with the given server ID (%d) is running and attached to system dbus under %s.' % ( srvid, self.meta );
|
||||
|
||||
return dbus.Interface( dbus.SystemBus().get_object( self.dbus_base, '/%d' % srvid ), 'net.sourceforge.mumble.Murmur' );
|
||||
|
||||
def getVersion( self ):
|
||||
return MumbleCtlDbus_118.convertDbusTypeToNative( self.meta.getVersion() );
|
||||
|
||||
def getAllConf(self, srvid):
|
||||
conf = self.meta.getAllConf(dbus.Int32(srvid))
|
||||
|
||||
info = {};
|
||||
for key in conf:
|
||||
if key == "playername":
|
||||
info['username'] = conf[key];
|
||||
else:
|
||||
info[str(key)] = conf[key];
|
||||
return info;
|
||||
|
||||
def getConf(self, srvid, key, value):
|
||||
if key == "username":
|
||||
key = "playername";
|
||||
|
||||
return self.meta.getConf(dbus.Int32( srvid ), key)
|
||||
|
||||
def setConf(self, srvid, key, value):
|
||||
if key == "username":
|
||||
key = "playername";
|
||||
|
||||
self.meta.setConf(dbus.Int32( srvid ), key, value)
|
||||
|
||||
def getDefaultConf(self):
|
||||
conf = self.meta.getDefaultConf()
|
||||
|
||||
info = {};
|
||||
for key in conf:
|
||||
if key == "playername":
|
||||
info['username'] = conf[key];
|
||||
else:
|
||||
info[str(key)] = conf[key];
|
||||
return info;
|
||||
|
||||
def start( self, srvid ):
|
||||
self.meta.start( srvid );
|
||||
|
||||
def stop( self, srvid ):
|
||||
self.meta.stop( srvid );
|
||||
|
||||
def isBooted( self, srvid ):
|
||||
return bool( self.meta.isBooted( srvid ) );
|
||||
|
||||
def deleteServer( self, srvid ):
|
||||
srvid = dbus.Int32( srvid )
|
||||
if self.meta.isBooted( srvid ):
|
||||
self.meta.stop( srvid )
|
||||
|
||||
self.meta.deleteServer( srvid )
|
||||
|
||||
def newServer(self):
|
||||
return self.meta.newServer()
|
||||
|
||||
def registerPlayer(self, srvid, name, email, password):
|
||||
mumbleid = int( self._getDbusServerObject(srvid).registerPlayer(name) );
|
||||
self.setRegistration( srvid, mumbleid, name, email, password );
|
||||
return mumbleid;
|
||||
|
||||
def unregisterPlayer(self, srvid, mumbleid):
|
||||
self._getDbusServerObject(srvid).unregisterPlayer(dbus.Int32( mumbleid ))
|
||||
|
||||
def getChannels(self, srvid):
|
||||
chans = self._getDbusServerObject(srvid).getChannels()
|
||||
|
||||
ret = {};
|
||||
|
||||
for channel in chans:
|
||||
ret[ channel[0] ] = ObjectInfo(
|
||||
id = int(channel[0]),
|
||||
name = str(channel[1]),
|
||||
parent = int(channel[2]),
|
||||
links = [ int(lnk) for lnk in channel[3] ],
|
||||
);
|
||||
|
||||
return ret;
|
||||
|
||||
def getPlayers(self, srvid):
|
||||
players = self._getDbusServerObject(srvid).getPlayers();
|
||||
|
||||
ret = {};
|
||||
|
||||
for playerObj in players:
|
||||
ret[ int(playerObj[0]) ] = ObjectInfo(
|
||||
session = int( playerObj[0] ),
|
||||
mute = bool( playerObj[1] ),
|
||||
deaf = bool( playerObj[2] ),
|
||||
suppress = bool( playerObj[3] ),
|
||||
selfMute = bool( playerObj[4] ),
|
||||
selfDeaf = bool( playerObj[5] ),
|
||||
channel = int( playerObj[6] ),
|
||||
userid = int( playerObj[7] ),
|
||||
name = str( playerObj[8] ),
|
||||
onlinesecs = int( playerObj[9] ),
|
||||
bytespersec = int( playerObj[10] )
|
||||
);
|
||||
|
||||
return ret;
|
||||
|
||||
def getRegisteredPlayers(self, srvid, filter = ''):
|
||||
users = self._getDbusServerObject(srvid).getRegisteredPlayers( filter );
|
||||
ret = {};
|
||||
|
||||
for user in users:
|
||||
ret[int(user[0])] = ObjectInfo(
|
||||
userid = int( user[0] ),
|
||||
name = unicode( user[1] ),
|
||||
email = unicode( user[2] ),
|
||||
pw = unicode( user[3] )
|
||||
);
|
||||
|
||||
return ret
|
||||
|
||||
def getACL(self, srvid, channelid):
|
||||
raw_acls, raw_groups, raw_inherit = self._getDbusServerObject(srvid).getACL(channelid)
|
||||
|
||||
acls = [ ObjectInfo(
|
||||
applyHere = bool(rule[0]),
|
||||
applySubs = bool(rule[1]),
|
||||
inherited = bool(rule[2]),
|
||||
userid = int(rule[3]),
|
||||
group = str(rule[4]),
|
||||
allow = int(rule[5]),
|
||||
deny = int(rule[6]),
|
||||
)
|
||||
for rule in raw_acls
|
||||
];
|
||||
|
||||
groups = [ ObjectInfo(
|
||||
name = str(group[0]),
|
||||
inherited = bool(group[1]),
|
||||
inherit = bool(group[2]),
|
||||
inheritable = bool(group[3]),
|
||||
add = [ int(usrid) for usrid in group[4] ],
|
||||
remove = [ int(usrid) for usrid in group[5] ],
|
||||
members = [ int(usrid) for usrid in group[6] ],
|
||||
)
|
||||
for group in raw_groups
|
||||
];
|
||||
|
||||
return acls, groups, bool(raw_inherit);
|
||||
|
||||
def setACL(self, srvid, channelid, acls, groups, inherit):
|
||||
# Pack acl ObjectInfo into a tuple and send that over dbus
|
||||
dbus_acls = [
|
||||
( rule.applyHere, rule.applySubs, rule.inherited, rule.userid, rule.group, rule.allow, rule.deny )
|
||||
for rule in acls
|
||||
];
|
||||
|
||||
dbus_groups = [
|
||||
( group.name, group.inherited, group.inherit, group.inheritable, group.add, group.remove, group.members )
|
||||
for group in groups
|
||||
];
|
||||
|
||||
return self._getDbusServerObject(srvid).setACL( channelid, dbus_acls, dbus_groups, inherit );
|
||||
|
||||
def getBootedServers(self):
|
||||
return MumbleCtlDbus_118.convertDbusTypeToNative(self.meta.getBootedServers())
|
||||
|
||||
def getAllServers(self):
|
||||
return MumbleCtlDbus_118.convertDbusTypeToNative(self.meta.getAllServers())
|
||||
|
||||
def setSuperUserPassword(self, srvid, value):
|
||||
self.meta.setSuperUserPassword(dbus.Int32(srvid), value)
|
||||
|
||||
def getRegistration(self, srvid, mumbleid):
|
||||
user = self._getDbusServerObject(srvid).getRegistration(dbus.Int32(mumbleid))
|
||||
return ObjectInfo(
|
||||
userid = mumbleid,
|
||||
name = unicode(user[1]),
|
||||
email = unicode(user[2]),
|
||||
pw = '',
|
||||
);
|
||||
|
||||
def setRegistration(self, srvid, mumbleid, name, email, password):
|
||||
return MumbleCtlDbus_118.convertDbusTypeToNative(
|
||||
self._getDbusServerObject(srvid).setRegistration(dbus.Int32(mumbleid), name, email, password)
|
||||
)
|
||||
|
||||
def getTexture(self, srvid, mumbleid):
|
||||
texture = self._getDbusServerObject(srvid).getTexture(dbus.Int32(mumbleid));
|
||||
|
||||
if len(texture) == 0:
|
||||
raise ValueError( "No Texture has been set." );
|
||||
# this returns a list of bytes.
|
||||
# first 4 bytes: Length of uncompressed string, rest: compressed data
|
||||
orig_len = ( texture[0] << 24 ) | ( texture[1] << 16 ) | ( texture[2] << 8 ) | ( texture[3] );
|
||||
# convert rest to string and run decompress
|
||||
bytestr = "";
|
||||
for byte in texture[4:]:
|
||||
bytestr += pack( "B", int(byte) );
|
||||
decompressed = decompress( bytestr );
|
||||
# iterate over 4 byte chunks of the string
|
||||
imgdata = "";
|
||||
for idx in range( 0, orig_len, 4 ):
|
||||
# read 4 bytes = BGRA and convert to RGBA
|
||||
bgra = unpack( "4B", decompressed[idx:idx+4] );
|
||||
imgdata += pack( "4B", bgra[2], bgra[1], bgra[0], bgra[3] );
|
||||
|
||||
# return an 600x60 RGBA image object created from the data
|
||||
return Image.fromstring( "RGBA", ( 600, 60 ), imgdata);
|
||||
|
||||
def setTexture(self, srvid, mumbleid, infile):
|
||||
# open image, convert to RGBA, and resize to 600x60
|
||||
img = Image.open( infile ).convert( "RGBA" ).transform( ( 600, 60 ), Image.EXTENT, ( 0, 0, 600, 60 ) );
|
||||
# iterate over the list and pack everything into a string
|
||||
bgrastring = "";
|
||||
for ent in list( img.getdata() ):
|
||||
# ent is in RGBA format, but Murmur wants BGRA (ARGB inverse), so stuff needs
|
||||
# to be reordered when passed to pack()
|
||||
bgrastring += pack( "4B", ent[2], ent[1], ent[0], ent[3] );
|
||||
# compress using zlib
|
||||
compressed = compress( bgrastring );
|
||||
# pack the original length in 4 byte big endian, and concat the compressed
|
||||
# data to it to emulate qCompress().
|
||||
texture = pack( ">L", len(bgrastring) ) + compressed;
|
||||
# finally call murmur and set the texture
|
||||
self._getDbusServerObject(srvid).setTexture(dbus.Int32( mumbleid ), texture)
|
||||
|
||||
def verifyPassword( self, srvid, username, password ):
|
||||
player = self.getRegisteredPlayers( srvid, username );
|
||||
if not player:
|
||||
return -2;
|
||||
|
||||
ok = MumbleCtlDbus_118.convertDbusTypeToNative(
|
||||
self._getDbusServerObject(srvid).verifyPassword( dbus.Int32( player[0].userid ), password )
|
||||
);
|
||||
|
||||
if ok:
|
||||
return player[0].userid;
|
||||
else:
|
||||
return -1;
|
||||
|
||||
@staticmethod
|
||||
def convertDbusTypeToNative(data):
|
||||
#i know dbus.* type is extends python native type.
|
||||
#but dbus.* type is not native type. it's not good transparent for using Ice/Dbus.
|
||||
ret = None
|
||||
|
||||
if isinstance(data, tuple) or type(data) is data.__class__ is dbus.Array or data.__class__ is dbus.Struct:
|
||||
ret = []
|
||||
for x in data:
|
||||
ret.append(MumbleCtlDbus_118.convertDbusTypeToNative(x))
|
||||
elif data.__class__ is dbus.Dictionary:
|
||||
ret = {}
|
||||
for x in data.items():
|
||||
ret[MumbleCtlDbus_118.convertDbusTypeToNative(x[0])] = MumbleCtlDbus_118.convertDbusTypeToNative(x[1])
|
||||
else:
|
||||
if data.__class__ is dbus.Boolean:
|
||||
ret = bool(data)
|
||||
elif data.__class__ is dbus.String:
|
||||
ret = unicode(data)
|
||||
elif data.__class__ is dbus.Int32 or data.__class__ is dbus.UInt32:
|
||||
ret = int(data)
|
||||
elif data.__class__ is dbus.Byte:
|
||||
ret = int(data)
|
||||
return ret
|
||||
|
||||
|
||||
class MumbleCtlDbus_Legacy( MumbleCtlDbus_118 ):
|
||||
def getVersion( self ):
|
||||
return ( 1, 1, 4, u"1.1.4" );
|
||||
|
||||
def setRegistration(self, srvid, mumbleid, name, email, password):
|
||||
return MumbleCtlDbus_118.convertDbusTypeToNative(
|
||||
self._getDbusServerObject(srvid).updateRegistration( ( dbus.Int32(mumbleid), name, email, password ) )
|
||||
)
|
||||
|
||||
|
||||
|
||||
|
||||
436
mumble/MumbleCtlIce.py
Normal file
436
mumble/MumbleCtlIce.py
Normal file
@@ -0,0 +1,436 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009, withgod <withgod@sourceforge.net>
|
||||
* 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
from os.path import exists
|
||||
from PIL import Image
|
||||
from struct import pack, unpack
|
||||
from zlib import compress, decompress
|
||||
|
||||
from mctl import MumbleCtlBase
|
||||
|
||||
from utils import ObjectInfo
|
||||
|
||||
import Ice
|
||||
|
||||
|
||||
def protectDjangoErrPage( func ):
|
||||
""" Catch and reraise Ice exceptions to prevent the Django page from failing.
|
||||
|
||||
Since I need to "import Murmur", Django would try to read a murmur.py file
|
||||
which doesn't exist, and thereby produce an IndexError exception. This method
|
||||
erases the exception's traceback, preventing Django from trying to read any
|
||||
non-existant files and borking.
|
||||
"""
|
||||
|
||||
def protection_wrapper( *args, **kwargs ):
|
||||
""" Call the original function and catch Ice exceptions. """
|
||||
try:
|
||||
return func( *args, **kwargs );
|
||||
except Ice.Exception, e:
|
||||
raise e;
|
||||
protection_wrapper.innerfunc = func
|
||||
|
||||
return protection_wrapper;
|
||||
|
||||
|
||||
|
||||
@protectDjangoErrPage
|
||||
def MumbleCtlIce( connstring, slicefile ):
|
||||
""" Choose the correct Ice handler to use (1.1.8 or 1.2.x), and make sure the
|
||||
Murmur version matches the slice Version.
|
||||
"""
|
||||
|
||||
try:
|
||||
import Murmur
|
||||
|
||||
except ImportError:
|
||||
if not slicefile:
|
||||
raise EnvironmentError( "You didn't configure a slice file. Please set the SLICE variable in settings.py." )
|
||||
|
||||
if not exists( slicefile ):
|
||||
raise EnvironmentError( "The slice file does not exist: '%s' - please check the settings." % slicefile )
|
||||
|
||||
if " " in slicefile:
|
||||
raise EnvironmentError( "You have a space char in your Slice path. This will confuse Ice, please check." )
|
||||
|
||||
if not slicefile.endswith( ".ice" ):
|
||||
raise EnvironmentError( "The slice file name MUST end with '.ice'." )
|
||||
|
||||
Ice.loadSlice( slicefile )
|
||||
|
||||
import Murmur
|
||||
|
||||
ice = Ice.initialize()
|
||||
prx = ice.stringToProxy( connstring.encode("utf-8") )
|
||||
meta = Murmur.MetaPrx.checkedCast(prx)
|
||||
|
||||
murmurversion = meta.getVersion()[:3]
|
||||
|
||||
if murmurversion == (1, 1, 8):
|
||||
return MumbleCtlIce_118( connstring, meta );
|
||||
|
||||
elif murmurversion[:2] == (1, 2):
|
||||
return MumbleCtlIce_120( connstring, meta );
|
||||
|
||||
|
||||
class MumbleCtlIce_118(MumbleCtlBase):
|
||||
method = "ICE";
|
||||
|
||||
def __init__( self, connstring, meta ):
|
||||
self.proxy = connstring;
|
||||
self.meta = meta;
|
||||
|
||||
@protectDjangoErrPage
|
||||
def _getIceServerObject(self, srvid):
|
||||
return self.meta.getServer(srvid);
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getBootedServers(self):
|
||||
ret = []
|
||||
for x in self.meta.getBootedServers():
|
||||
ret.append(x.id())
|
||||
return ret
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getVersion( self ):
|
||||
return self.meta.getVersion();
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getAllServers(self):
|
||||
ret = []
|
||||
for x in self.meta.getAllServers():
|
||||
ret.append(x.id())
|
||||
return ret
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getRegisteredPlayers(self, srvid, filter = ''):
|
||||
users = self._getIceServerObject(srvid).getRegisteredPlayers( filter.encode( "UTF-8" ) )
|
||||
ret = {};
|
||||
|
||||
for user in users:
|
||||
ret[user.playerid] = ObjectInfo(
|
||||
userid = int( user.playerid ),
|
||||
name = unicode( user.name, "utf8" ),
|
||||
email = unicode( user.email, "utf8" ),
|
||||
pw = unicode( user.pw, "utf8" )
|
||||
);
|
||||
|
||||
return ret
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getChannels(self, srvid):
|
||||
return self._getIceServerObject(srvid).getChannels();
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getPlayers(self, srvid):
|
||||
users = self._getIceServerObject(srvid).getPlayers()
|
||||
|
||||
ret = {};
|
||||
|
||||
for useridx in users:
|
||||
user = users[useridx];
|
||||
ret[ user.session ] = ObjectInfo(
|
||||
session = user.session,
|
||||
userid = user.playerid,
|
||||
mute = user.mute,
|
||||
deaf = user.deaf,
|
||||
suppress = user.suppressed,
|
||||
selfMute = user.selfMute,
|
||||
selfDeaf = user.selfDeaf,
|
||||
channel = user.channel,
|
||||
name = user.name,
|
||||
onlinesecs = user.onlinesecs,
|
||||
bytespersec = user.bytespersec
|
||||
);
|
||||
|
||||
return ret;
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getDefaultConf(self):
|
||||
return self.setUnicodeFlag(self.meta.getDefaultConf())
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getAllConf(self, srvid):
|
||||
conf = self.setUnicodeFlag(self._getIceServerObject(srvid).getAllConf())
|
||||
|
||||
info = {};
|
||||
for key in conf:
|
||||
if key == "playername":
|
||||
info['username'] = conf[key];
|
||||
else:
|
||||
info[str(key)] = conf[key];
|
||||
return info;
|
||||
|
||||
@protectDjangoErrPage
|
||||
def newServer(self):
|
||||
return self.meta.newServer().id()
|
||||
|
||||
@protectDjangoErrPage
|
||||
def isBooted( self, srvid ):
|
||||
return bool( self._getIceServerObject(srvid).isRunning() );
|
||||
|
||||
@protectDjangoErrPage
|
||||
def start( self, srvid ):
|
||||
self._getIceServerObject(srvid).start();
|
||||
|
||||
@protectDjangoErrPage
|
||||
def stop( self, srvid ):
|
||||
self._getIceServerObject(srvid).stop();
|
||||
|
||||
@protectDjangoErrPage
|
||||
def deleteServer( self, srvid ):
|
||||
if self._getIceServerObject(srvid).isRunning():
|
||||
self._getIceServerObject(srvid).stop()
|
||||
self._getIceServerObject(srvid).delete()
|
||||
|
||||
@protectDjangoErrPage
|
||||
def setSuperUserPassword(self, srvid, value):
|
||||
self._getIceServerObject(srvid).setSuperuserPassword( value.encode( "UTF-8" ) )
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getConf(self, srvid, key):
|
||||
if key == "username":
|
||||
key = "playername";
|
||||
|
||||
return self._getIceServerObject(srvid).getConf( key )
|
||||
|
||||
@protectDjangoErrPage
|
||||
def setConf(self, srvid, key, value):
|
||||
if key == "username":
|
||||
key = "playername";
|
||||
if value is None:
|
||||
value = ''
|
||||
self._getIceServerObject(srvid).setConf( key, value.encode( "UTF-8" ) )
|
||||
|
||||
@protectDjangoErrPage
|
||||
def registerPlayer(self, srvid, name, email, password):
|
||||
mumbleid = self._getIceServerObject(srvid).registerPlayer( name.encode( "UTF-8" ) )
|
||||
self.setRegistration( srvid, mumbleid, name, email, password );
|
||||
return mumbleid;
|
||||
|
||||
@protectDjangoErrPage
|
||||
def unregisterPlayer(self, srvid, mumbleid):
|
||||
self._getIceServerObject(srvid).unregisterPlayer(mumbleid)
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getRegistration(self, srvid, mumbleid):
|
||||
user = self._getIceServerObject(srvid).getRegistration(mumbleid)
|
||||
return ObjectInfo(
|
||||
userid = mumbleid,
|
||||
name = user.name,
|
||||
email = user.email,
|
||||
pw = '',
|
||||
);
|
||||
|
||||
@protectDjangoErrPage
|
||||
def setRegistration(self, srvid, mumbleid, name, email, password):
|
||||
import Murmur
|
||||
user = Murmur.Player()
|
||||
user.playerid = mumbleid;
|
||||
user.name = name.encode( "UTF-8" )
|
||||
user.email = email.encode( "UTF-8" )
|
||||
user.pw = password.encode( "UTF-8" )
|
||||
# update*r*egistration r is lowercase...
|
||||
return self._getIceServerObject(srvid).updateregistration(user)
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getACL(self, srvid, channelid):
|
||||
# need to convert acls to say "userid" instead of "playerid". meh.
|
||||
raw_acls, raw_groups, raw_inherit = self._getIceServerObject(srvid).getACL(channelid)
|
||||
|
||||
acls = [ ObjectInfo(
|
||||
applyHere = rule.applyHere,
|
||||
applySubs = rule.applySubs,
|
||||
inherited = rule.inherited,
|
||||
userid = rule.playerid,
|
||||
group = rule.group,
|
||||
allow = rule.allow,
|
||||
deny = rule.deny,
|
||||
)
|
||||
for rule in raw_acls
|
||||
];
|
||||
|
||||
return acls, raw_groups, raw_inherit;
|
||||
|
||||
@protectDjangoErrPage
|
||||
def setACL(self, srvid, channelid, acls, groups, inherit):
|
||||
import Murmur
|
||||
|
||||
ice_acls = [];
|
||||
|
||||
for rule in acls:
|
||||
ice_rule = Murmur.ACL();
|
||||
ice_rule.applyHere = rule.applyHere;
|
||||
ice_rule.applySubs = rule.applySubs;
|
||||
ice_rule.inherited = rule.inherited;
|
||||
ice_rule.playerid = rule.userid;
|
||||
ice_rule.group = rule.group;
|
||||
ice_rule.allow = rule.allow;
|
||||
ice_rule.deny = rule.deny;
|
||||
ice_acls.append(ice_rule);
|
||||
|
||||
return self._getIceServerObject(srvid).setACL( channelid, ice_acls, groups, inherit );
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getTexture(self, srvid, mumbleid):
|
||||
texture = self._getIceServerObject(srvid).getTexture(mumbleid)
|
||||
if len(texture) == 0:
|
||||
raise ValueError( "No Texture has been set." );
|
||||
# this returns a list of bytes.
|
||||
decompressed = decompress( texture );
|
||||
# iterate over 4 byte chunks of the string
|
||||
imgdata = "";
|
||||
for idx in range( 0, len(decompressed), 4 ):
|
||||
# read 4 bytes = BGRA and convert to RGBA
|
||||
# manual wrote getTexture returns "Textures are stored as zlib compress()ed 600x60 32-bit RGBA data."
|
||||
# http://mumble.sourceforge.net/slice/Murmur/Server.html#getTexture
|
||||
# but return values BGRA X(
|
||||
bgra = unpack( "4B", decompressed[idx:idx+4] );
|
||||
imgdata += pack( "4B", bgra[2], bgra[1], bgra[0], bgra[3] );
|
||||
|
||||
# return an 600x60 RGBA image object created from the data
|
||||
return Image.fromstring( "RGBA", ( 600, 60 ), imgdata );
|
||||
|
||||
@protectDjangoErrPage
|
||||
def setTexture(self, srvid, mumbleid, infile):
|
||||
# open image, convert to RGBA, and resize to 600x60
|
||||
img = Image.open( infile ).convert( "RGBA" ).transform( ( 600, 60 ), Image.EXTENT, ( 0, 0, 600, 60 ) );
|
||||
# iterate over the list and pack everything into a string
|
||||
bgrastring = "";
|
||||
for ent in list( img.getdata() ):
|
||||
# ent is in RGBA format, but Murmur wants BGRA (ARGB inverse), so stuff needs
|
||||
# to be reordered when passed to pack()
|
||||
bgrastring += pack( "4B", ent[2], ent[1], ent[0], ent[3] );
|
||||
# compress using zlib
|
||||
compressed = compress( bgrastring );
|
||||
# pack the original length in 4 byte big endian, and concat the compressed
|
||||
# data to it to emulate qCompress().
|
||||
texture = pack( ">L", len(bgrastring) ) + compressed;
|
||||
# finally call murmur and set the texture
|
||||
self._getIceServerObject(srvid).setTexture(mumbleid, texture)
|
||||
|
||||
@protectDjangoErrPage
|
||||
def verifyPassword(self, srvid, username, password):
|
||||
return self._getIceServerObject(srvid).verifyPassword(username, password);
|
||||
|
||||
@staticmethod
|
||||
def setUnicodeFlag(data):
|
||||
ret = ''
|
||||
if isinstance(data, tuple) or isinstance(data, list) or isinstance(data, dict):
|
||||
ret = {}
|
||||
for key in data.keys():
|
||||
ret[MumbleCtlIce_118.setUnicodeFlag(key)] = MumbleCtlIce_118.setUnicodeFlag(data[key])
|
||||
else:
|
||||
ret = unicode(data, 'utf-8')
|
||||
|
||||
return ret
|
||||
|
||||
|
||||
|
||||
|
||||
class MumbleCtlIce_120(MumbleCtlIce_118):
|
||||
@protectDjangoErrPage
|
||||
def getRegisteredPlayers(self, srvid, filter = ''):
|
||||
users = self._getIceServerObject( srvid ).getRegisteredUsers( filter.encode( "UTF-8" ) )
|
||||
ret = {};
|
||||
|
||||
for id in users:
|
||||
ret[id] = ObjectInfo(
|
||||
userid = id,
|
||||
name = unicode( users[id], "utf8" ),
|
||||
email = '',
|
||||
pw = ''
|
||||
);
|
||||
|
||||
return ret
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getPlayers(self, srvid):
|
||||
return self._getIceServerObject(srvid).getUsers();
|
||||
|
||||
@protectDjangoErrPage
|
||||
def registerPlayer(self, srvid, name, email, password):
|
||||
# To get the real values of these ENUM entries, try
|
||||
# Murmur.UserInfo.UserX.value
|
||||
import Murmur
|
||||
user = {
|
||||
Murmur.UserInfo.UserName: name.encode( "UTF-8" ),
|
||||
Murmur.UserInfo.UserEmail: email.encode( "UTF-8" ),
|
||||
Murmur.UserInfo.UserPassword: password.encode( "UTF-8" ),
|
||||
};
|
||||
return self._getIceServerObject(srvid).registerUser( user );
|
||||
|
||||
@protectDjangoErrPage
|
||||
def unregisterPlayer(self, srvid, mumbleid):
|
||||
self._getIceServerObject(srvid).unregisterUser(mumbleid)
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getRegistration(self, srvid, mumbleid):
|
||||
reg = self._getIceServerObject( srvid ).getRegistration( mumbleid )
|
||||
user = ObjectInfo( userid=mumbleid, name="", email="", comment="", hash="", pw="" );
|
||||
import Murmur
|
||||
if Murmur.UserInfo.UserName in reg: user.name = reg[Murmur.UserInfo.UserName];
|
||||
if Murmur.UserInfo.UserEmail in reg: user.email = reg[Murmur.UserInfo.UserEmail];
|
||||
if Murmur.UserInfo.UserComment in reg: user.comment = reg[Murmur.UserInfo.UserComment];
|
||||
if Murmur.UserInfo.UserHash in reg: user.hash = reg[Murmur.UserInfo.UserHash];
|
||||
return user;
|
||||
|
||||
@protectDjangoErrPage
|
||||
def setRegistration(self, srvid, mumbleid, name, email, password):
|
||||
import Murmur
|
||||
user = {
|
||||
Murmur.UserInfo.UserName: name.encode( "UTF-8" ),
|
||||
Murmur.UserInfo.UserEmail: email.encode( "UTF-8" ),
|
||||
Murmur.UserInfo.UserPassword: password.encode( "UTF-8" ),
|
||||
};
|
||||
return self._getIceServerObject( srvid ).updateRegistration( mumbleid, user )
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getAllConf(self, srvid):
|
||||
conf = self.setUnicodeFlag(self._getIceServerObject(srvid).getAllConf())
|
||||
|
||||
info = {};
|
||||
for key in conf:
|
||||
if key == "playername" and conf[key]:
|
||||
# Buggy database transition from 1.1.8 -> 1.2.0
|
||||
# Store username as "username" field and set playername field to empty
|
||||
info['username'] = conf[key];
|
||||
self.setConf( srvid, "playername", "" );
|
||||
self.setConf( srvid, "username", conf[key] );
|
||||
else:
|
||||
info[str(key)] = conf[key];
|
||||
|
||||
return info;
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getConf(self, srvid, key):
|
||||
return self._getIceServerObject(srvid).getConf( key )
|
||||
|
||||
@protectDjangoErrPage
|
||||
def setConf(self, srvid, key, value):
|
||||
if value is None:
|
||||
value = ''
|
||||
self._getIceServerObject(srvid).setConf( key, value.encode( "UTF-8" ) )
|
||||
|
||||
@protectDjangoErrPage
|
||||
def getACL(self, srvid, channelid):
|
||||
return self._getIceServerObject(srvid).getACL(channelid)
|
||||
|
||||
@protectDjangoErrPage
|
||||
def setACL(self, srvid, channelid, acls, groups, inherit):
|
||||
return self._getIceServerObject(srvid).setACL( channelid, acls, groups, inherit );
|
||||
|
||||
14
mumble/__init__.py
Normal file
14
mumble/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
101
mumble/admin.py
Normal file
101
mumble/admin.py
Normal file
@@ -0,0 +1,101 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
from django.contrib import admin
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
#from mumble.forms import MumbleAdminForm, MumbleUserAdminForm
|
||||
from mumble.models import Mumble, MumbleUser
|
||||
|
||||
class MumbleAdmin(admin.ModelAdmin):
|
||||
""" Specification for the "Server administration" admin section. """
|
||||
|
||||
list_display = [ 'name', 'addr', 'port', 'get_booted', 'get_is_public',
|
||||
'get_users_regged', 'get_users_online', 'get_channel_count' ];
|
||||
list_filter = [ 'addr' ];
|
||||
search_fields = [ 'name', 'addr', 'port' ];
|
||||
ordering = [ 'name' ];
|
||||
#form = MumbleAdminForm;
|
||||
|
||||
|
||||
def get_booted( self, obj ):
|
||||
return obj.booted
|
||||
|
||||
get_booted.short_description = _('Boot Server')
|
||||
get_booted.boolean = True
|
||||
|
||||
def get_users_regged( self, obj ):
|
||||
""" Populates the "Registered users" column. """
|
||||
if obj.booted:
|
||||
return obj.users_regged;
|
||||
else:
|
||||
return '-';
|
||||
|
||||
get_users_regged.short_description = _( 'Registered users' );
|
||||
|
||||
|
||||
def get_users_online( self, obj ):
|
||||
""" Populates the "Online users" column. """
|
||||
if obj.booted:
|
||||
return obj.users_online;
|
||||
else:
|
||||
return '-';
|
||||
|
||||
get_users_online.short_description = _( 'Online users' );
|
||||
|
||||
|
||||
def get_channel_count( self, obj ):
|
||||
""" Populates the "Channel Count" column. """
|
||||
if obj.booted:
|
||||
return obj.channel_cnt;
|
||||
else:
|
||||
return '-';
|
||||
|
||||
get_channel_count.short_description = _( 'Channel count' );
|
||||
|
||||
|
||||
def get_is_public( self, obj ):
|
||||
""" Populates the "Public" column. """
|
||||
if obj.booted:
|
||||
if obj.is_public:
|
||||
return _( 'Yes' );
|
||||
else:
|
||||
return _( 'No' );
|
||||
else:
|
||||
return '-';
|
||||
|
||||
get_is_public.short_description = _( 'Public' );
|
||||
|
||||
|
||||
class MumbleUserAdmin(admin.ModelAdmin):
|
||||
""" Specification for the "Registered users" admin section. """
|
||||
|
||||
list_display = [ 'owner', 'server', 'name', 'get_acl_admin' ];
|
||||
list_filter = [ 'server' ];
|
||||
search_fields = [ 'owner__username', 'name' ];
|
||||
ordering = [ 'owner__username' ];
|
||||
|
||||
#form = MumbleUserAdminForm
|
||||
|
||||
def get_acl_admin( self, obj ):
|
||||
return obj.aclAdmin
|
||||
|
||||
get_acl_admin.short_description = _('Admin on root channel')
|
||||
get_acl_admin.boolean = True
|
||||
|
||||
|
||||
admin.site.register( Mumble, MumbleAdmin );
|
||||
admin.site.register( MumbleUser, MumbleUserAdmin );
|
||||
22
mumble/management/__init__.py
Normal file
22
mumble/management/__init__.py
Normal file
@@ -0,0 +1,22 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
from server_detect import find_existing_instances
|
||||
from django.db.models import signals
|
||||
from mumble import models
|
||||
|
||||
signals.post_syncdb.connect( find_existing_instances, sender=models );
|
||||
|
||||
15
mumble/management/commands/__init__.py
Normal file
15
mumble/management/commands/__init__.py
Normal file
@@ -0,0 +1,15 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
194
mumble/management/commands/checkenv.py
Normal file
194
mumble/management/commands/checkenv.py
Normal file
@@ -0,0 +1,194 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
import os, Ice
|
||||
|
||||
from django.core.management.base import BaseCommand
|
||||
from django.contrib.auth.models import User
|
||||
from django.contrib.sites.models import Site
|
||||
from django.conf import settings
|
||||
|
||||
from mumble.models import Mumble
|
||||
|
||||
|
||||
class TestFailed( Exception ):
|
||||
pass;
|
||||
|
||||
class Command( BaseCommand ):
|
||||
def handle(self, **options):
|
||||
self.check_slice();
|
||||
self.check_rootdir();
|
||||
self.check_dbase();
|
||||
self.check_sites();
|
||||
self.check_mumbles();
|
||||
self.check_admins();
|
||||
self.check_secret_key();
|
||||
|
||||
def check_slice( self ):
|
||||
print "Checking slice file...",
|
||||
if settings.SLICE is None:
|
||||
raise TestFailed( "You don't have set the SLICE variable in settings.py." )
|
||||
|
||||
if " " in settings.SLICE:
|
||||
raise TestFailed( "You have a space char in your Slice path. This will confuse Ice, please check." )
|
||||
|
||||
if not settings.SLICE.endswith( ".ice" ):
|
||||
raise TestFailed( "The slice file name MUST end with '.ice'." )
|
||||
|
||||
try:
|
||||
fd = open( settings.SLICE, "rb" )
|
||||
slice = fd.read()
|
||||
fd.close()
|
||||
except IOError, err:
|
||||
raise TestFailed( "Failed opening the slice file: %s" % err )
|
||||
|
||||
import Ice
|
||||
Ice.loadSlice( settings.SLICE )
|
||||
|
||||
print "[ OK ]"
|
||||
|
||||
def check_rootdir( self ):
|
||||
print "Checking root directory access...",
|
||||
if not os.path.exists( settings.MUMBLE_DJANGO_ROOT ):
|
||||
raise TestFailed( "The mumble-django root directory does not exist." );
|
||||
|
||||
elif settings.DATABASE_ENGINE != "sqlite3":
|
||||
print "not using sqlite [ OK ]"
|
||||
|
||||
else:
|
||||
statinfo = os.stat( settings.MUMBLE_DJANGO_ROOT );
|
||||
|
||||
if statinfo.st_uid == 0:
|
||||
raise TestFailed(
|
||||
"The mumble-django root directory belongs to user root. This is "
|
||||
"most certainly not what you want because it will prevent your "
|
||||
"web server from being able to write to the database. Please check." );
|
||||
|
||||
elif not os.access( settings.MUMBLE_DJANGO_ROOT, os.W_OK ):
|
||||
raise TestFailed( "The mumble-django root directory is not writable." );
|
||||
|
||||
else:
|
||||
print "[ OK ]";
|
||||
|
||||
def check_dbase( self ):
|
||||
print "Checking database access...",
|
||||
if settings.DATABASE_ENGINE == "sqlite3":
|
||||
if not os.path.exists( settings.DATABASE_NAME ):
|
||||
raise TestFailed( "database does not exist. Have you run syncdb yet?" );
|
||||
|
||||
else:
|
||||
statinfo = os.stat( settings.DATABASE_NAME );
|
||||
|
||||
if statinfo.st_uid == 0:
|
||||
raise TestFailed(
|
||||
"the database file belongs to root. This is most certainly not what "
|
||||
"you want because it will prevent your web server from being able "
|
||||
"to write to it. Please check." );
|
||||
|
||||
elif not os.access( settings.DATABASE_NAME, os.W_OK ):
|
||||
raise TestFailed( "database file is not writable." );
|
||||
|
||||
else:
|
||||
print "[ OK ]";
|
||||
|
||||
else:
|
||||
print "not using sqlite, so I can't check.";
|
||||
|
||||
|
||||
def check_sites( self ):
|
||||
print "Checking URL configuration...",
|
||||
|
||||
try:
|
||||
site = Site.objects.get_current();
|
||||
|
||||
except Site.DoesNotExist:
|
||||
try:
|
||||
sid = settings.SITE_ID
|
||||
except AttributeError:
|
||||
from django.core.exceptions import ImproperlyConfigured
|
||||
raise ImproperlyConfigured(
|
||||
"You're using the Django \"sites framework\" without having set the SITE_ID "
|
||||
"setting. Create a site in your database and rerun this command to fix this error.")
|
||||
else:
|
||||
print( "none set.\n"
|
||||
"Please enter the domain where Mumble-Django is reachable." );
|
||||
dom = raw_input( "> " ).strip();
|
||||
site = Site( id=sid, name=dom, domain=dom );
|
||||
site.save();
|
||||
|
||||
if site.domain == 'example.com':
|
||||
print( "still the default.\n"
|
||||
"The domain is configured as example.com, which is the default but does not make sense. "
|
||||
"Please enter the domain where Mumble-Django is reachable." );
|
||||
|
||||
site.domain = raw_input( "> " ).strip();
|
||||
site.save();
|
||||
|
||||
print site.domain, "[ OK ]";
|
||||
|
||||
|
||||
def check_admins( self ):
|
||||
print "Checking if an Admin user exists...",
|
||||
|
||||
for user in User.objects.all():
|
||||
if user.is_superuser:
|
||||
print "[ OK ]";
|
||||
return;
|
||||
|
||||
raise TestFailed( ""
|
||||
"No admin user exists, so you won't be able to log in to the admin system. You "
|
||||
"should run `./manage.py createsuperuser` to create one." );
|
||||
|
||||
|
||||
def check_mumbles( self ):
|
||||
print "Checking Murmur instances...",
|
||||
|
||||
mm = Mumble.objects.all();
|
||||
|
||||
if mm.count() == 0:
|
||||
raise TestFailed(
|
||||
"no Mumble servers are configured, you might want to run "
|
||||
"`./manage.py syncdb` to run an auto detection." );
|
||||
|
||||
else:
|
||||
for mumble in mm:
|
||||
try:
|
||||
mumble.getCtl();
|
||||
except Ice.Exception, err:
|
||||
raise TestFailed(
|
||||
"Connecting to Murmur `%s` (%s) failed: %s" % ( mumble.name, mumble.dbus, err )
|
||||
);
|
||||
print "[ OK ]";
|
||||
|
||||
def check_secret_key( self ):
|
||||
print "Checking SECRET_KEY...",
|
||||
|
||||
blacklist = ( 'u-mp185msk#z4%s(do2^5405)y5d!9adbn92)apu_p^qvqh10v', );
|
||||
|
||||
if settings.SECRET_KEY in blacklist:
|
||||
raise TestFailed(
|
||||
"Your SECRET_KEY setting matches one of the keys that were put in the settings.py "
|
||||
"file shipped with Mumble-Django, which means your SECRET_KEY is all but secret. "
|
||||
"You should change the setting, or run gen_secret_key.sh to do it for you."
|
||||
);
|
||||
else:
|
||||
print "[ OK ]";
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
21
mumble/management/commands/mmrunserver.py
Normal file
21
mumble/management/commands/mmrunserver.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
from django.core.management.commands.runserver import Command as OrigCommand
|
||||
from mumble.murmurenvutils import MumbleCommandWrapper
|
||||
|
||||
class Command( MumbleCommandWrapper, OrigCommand ):
|
||||
pass
|
||||
21
mumble/management/commands/mmshell.py
Normal file
21
mumble/management/commands/mmshell.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
from django.core.management.commands.shell import Command as OrigCommand
|
||||
from mumble.murmurenvutils import MumbleCommandWrapper_noargs
|
||||
|
||||
class Command( MumbleCommandWrapper_noargs, OrigCommand ):
|
||||
pass
|
||||
21
mumble/management/commands/mmsyncdb.py
Normal file
21
mumble/management/commands/mmsyncdb.py
Normal file
@@ -0,0 +1,21 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
from django.core.management.commands.syncdb import Command as OrigCommand
|
||||
from mumble.murmurenvutils import MumbleCommandWrapper_noargs
|
||||
|
||||
class Command( MumbleCommandWrapper_noargs, OrigCommand ):
|
||||
pass
|
||||
135
mumble/management/server_detect.py
Normal file
135
mumble/management/server_detect.py
Normal file
@@ -0,0 +1,135 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from mumble import models
|
||||
from mumble.mctl import MumbleCtlBase
|
||||
|
||||
|
||||
def find_in_dicts( keys, conf, default, valueIfNotFound=None ):
|
||||
if not isinstance( keys, tuple ):
|
||||
keys = ( keys, );
|
||||
|
||||
for keyword in keys:
|
||||
if keyword in conf:
|
||||
return conf[keyword];
|
||||
|
||||
for keyword in keys:
|
||||
keyword = keyword.lower();
|
||||
if keyword in default:
|
||||
return default[keyword];
|
||||
|
||||
return valueIfNotFound;
|
||||
|
||||
|
||||
def find_existing_instances( **kwargs ):
|
||||
if "verbosity" in kwargs:
|
||||
v = kwargs['verbosity'];
|
||||
else:
|
||||
v = 1;
|
||||
|
||||
if v > 1:
|
||||
print "Starting Mumble servers and players detection now.";
|
||||
|
||||
triedEnviron = False;
|
||||
online = False;
|
||||
while not online:
|
||||
if not triedEnviron and 'MURMUR_CONNSTR' in os.environ:
|
||||
dbusName = os.environ['MURMUR_CONNSTR'];
|
||||
triedEnviron = True;
|
||||
if v > 1:
|
||||
print "Trying environment setting", dbusName;
|
||||
else:
|
||||
print "--- Murmur connection info ---"
|
||||
print " 1) DBus -- net.sourceforge.mumble.murmur"
|
||||
print " 2) ICE -- Meta:tcp -h 127.0.0.1 -p 6502"
|
||||
print "Enter 1 or 2 for the defaults above, nothing to skip Server detection,"
|
||||
print "and if the defaults do not fit your needs, enter the correct string."
|
||||
print "Whether to use DBus or ICE will be detected automatically from the"
|
||||
print "string's format."
|
||||
print
|
||||
|
||||
dbusName = raw_input( "Service string: " ).strip();
|
||||
|
||||
if not dbusName:
|
||||
if v:
|
||||
print 'Be sure to run "python manage.py syncdb" with Murmur running before'
|
||||
print "trying to use this app! Otherwise, existing Murmur servers won't be"
|
||||
print 'configurable!';
|
||||
return False;
|
||||
elif dbusName == "1":
|
||||
dbusName = "net.sourceforge.mumble.murmur";
|
||||
elif dbusName == "2":
|
||||
dbusName = "Meta:tcp -h 127.0.0.1 -p 6502";
|
||||
|
||||
try:
|
||||
ctl = MumbleCtlBase.newInstance( dbusName, settings.SLICE );
|
||||
except Exception, instance:
|
||||
if v:
|
||||
print "Unable to connect using name %s. The error was:" % dbusName;
|
||||
print instance;
|
||||
print
|
||||
else:
|
||||
online = True;
|
||||
if v > 1:
|
||||
print "Successfully connected to Murmur via connection string %s, using %s." % ( dbusName, ctl.method );
|
||||
|
||||
servIDs = ctl.getAllServers();
|
||||
|
||||
for id in servIDs:
|
||||
if v > 1:
|
||||
print "Checking Murmur instance with id %d." % id;
|
||||
# first check that the server has not yet been inserted into the DB
|
||||
try:
|
||||
instance = models.Mumble.objects.get( dbus=dbusName, srvid=id );
|
||||
except models.Mumble.DoesNotExist:
|
||||
values = {
|
||||
"srvid": id,
|
||||
"dbus": dbusName,
|
||||
}
|
||||
|
||||
if v > 1:
|
||||
print "Found new Murmur instance %d on bus '%s'... " % ( id, dbusName ),
|
||||
|
||||
# now create a model for the record set.
|
||||
instance = models.Mumble( **values );
|
||||
else:
|
||||
if v > 1:
|
||||
print "Syncing Murmur instance... ",
|
||||
|
||||
instance.configureFromMurmur();
|
||||
|
||||
if v > 1:
|
||||
print instance.name;
|
||||
|
||||
instance.save( dontConfigureMurmur=True );
|
||||
|
||||
# Now search for players on this server that have not yet been registered
|
||||
if instance.booted:
|
||||
if v > 1:
|
||||
print "Looking for registered Players on Server id %d." % id;
|
||||
instance.readUsersFromMurmur( verbose=v );
|
||||
elif v > 1:
|
||||
print "This server is not running, can't sync players.";
|
||||
|
||||
if v > 1:
|
||||
print "Successfully finished Servers and Players detection.";
|
||||
return True;
|
||||
|
||||
|
||||
128
mumble/mctl.py
Normal file
128
mumble/mctl.py
Normal file
@@ -0,0 +1,128 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009, withgod <withgod@sourceforge.net>
|
||||
* 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
import re
|
||||
|
||||
class MumbleCtlBase (object):
|
||||
""" This class defines the base interface that the Mumble model expects. """
|
||||
|
||||
cache = {};
|
||||
|
||||
def getAllConf(self, srvid):
|
||||
raise NotImplementedError( "mctl::getAllConf" );
|
||||
|
||||
def getVersion( self ):
|
||||
raise NotImplementedError( "mctl::getVersion" );
|
||||
|
||||
def getConf(self, srvid, key):
|
||||
raise NotImplementedError( "mctl::getConf" );
|
||||
|
||||
def setConf(self, srvid, key, value):
|
||||
raise NotImplementedError( "mctl::setConf" );
|
||||
|
||||
def getDefaultConf(self):
|
||||
raise NotImplementedError( "mctl::getDefaultConf" );
|
||||
|
||||
def newServer(self):
|
||||
raise NotImplementedError( "mctl::newServer" );
|
||||
|
||||
def setSuperUserPassword(self, srvid, value):
|
||||
raise NotImplementedError( "mctl::setSuperUserPassword" );
|
||||
|
||||
def start(self, srvid):
|
||||
raise NotImplementedError( "mctl::start" );
|
||||
|
||||
def stop(self, srvid):
|
||||
raise NotImplementedError( "mctl::stop" );
|
||||
|
||||
def isBooted(self, srvid):
|
||||
raise NotImplementedError( "mctl::isBooted" );
|
||||
|
||||
def deleteServer(self, srvid):
|
||||
raise NotImplementedError( "mctl::deleteServer" );
|
||||
|
||||
def getPlayers(self, srvid):
|
||||
raise NotImplementedError( "mctl::getPlayers" );
|
||||
|
||||
def getRegisteredPlayers(self, srvid, filter):
|
||||
raise NotImplementedError( "mctl::getRegisteredPlayers" );
|
||||
|
||||
def getChannels(self, srvid):
|
||||
raise NotImplementedError( "mctl::getChannels" );
|
||||
|
||||
def registerPlayer(self, srvid, name, email, password):
|
||||
raise NotImplementedError( "mctl::registerPlayer" );
|
||||
|
||||
def getRegistration(self, srvid, mumbleid):
|
||||
raise NotImplementedError( "mctl::getRegistration" );
|
||||
|
||||
def setRegistration(self, srvid, mumbleid, name, email, password):
|
||||
raise NotImplementedError( "mctl::setRegistration" );
|
||||
|
||||
def unregisterPlayer(self, srvid, mumbleid):
|
||||
raise NotImplementedError( "mctl::unregisterPlayer" );
|
||||
|
||||
def getBootedServers(self):
|
||||
raise NotImplementedError( "mctl::getBootedServers" );
|
||||
|
||||
def getAllServers(self):
|
||||
raise NotImplementedError( "mctl::getAllServers" );
|
||||
|
||||
def getACL(self, srvid, channelid):
|
||||
raise NotImplementedError( "mctl::getACL" );
|
||||
|
||||
def setACL(self, srvid, channelid, acl, groups, inherit):
|
||||
raise NotImplementedError( "mctl::setACL" );
|
||||
|
||||
def getTexture(self, srvid, mumbleid):
|
||||
raise NotImplementedError( "mctl::getTexture" );
|
||||
|
||||
def setTexture(self, srvid, mumbleid, infile):
|
||||
raise NotImplementedError( "mctl::setTexture" );
|
||||
|
||||
def verifyPassword( self, srvid, username, password ):
|
||||
raise NotImplementedError( "mctl::verifyPassword" );
|
||||
|
||||
@staticmethod
|
||||
def newInstance( connstring, slicefile ):
|
||||
""" Create a new CTL object for the given connstring. """
|
||||
|
||||
# check cache
|
||||
if connstring in MumbleCtlBase.cache:
|
||||
return MumbleCtlBase.cache[connstring];
|
||||
|
||||
# connstring defines whether to connect via ICE or DBus.
|
||||
# Dbus service names: some.words.divided.by.periods
|
||||
# ICE specs are WAY more complex, so if DBus doesn't match, use ICE.
|
||||
rd = re.compile( r'^(\w+\.)*\w+$' );
|
||||
|
||||
if rd.match( connstring ):
|
||||
from MumbleCtlDbus import MumbleCtlDbus
|
||||
ctl = MumbleCtlDbus( connstring )
|
||||
else:
|
||||
from MumbleCtlIce import MumbleCtlIce
|
||||
ctl = MumbleCtlIce( connstring, slicefile )
|
||||
|
||||
MumbleCtlBase.cache[connstring] = ctl;
|
||||
return ctl;
|
||||
|
||||
@staticmethod
|
||||
def clearCache():
|
||||
MumbleCtlBase.cache = {};
|
||||
|
||||
|
||||
|
||||
284
mumble/mmobjects.py
Normal file
284
mumble/mmobjects.py
Normal file
@@ -0,0 +1,284 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
import datetime
|
||||
from time import time
|
||||
from os.path import join
|
||||
|
||||
from django.utils.http import urlquote
|
||||
from django.conf import settings
|
||||
|
||||
def cmp_names( left, rite ):
|
||||
""" Compare two objects by their name property. """
|
||||
return cmp( left.name, rite.name );
|
||||
|
||||
|
||||
class mmChannel( object ):
|
||||
""" Represents a channel in Murmur. """
|
||||
|
||||
def __init__( self, server, channel_obj, parent_chan = None ):
|
||||
self.server = server;
|
||||
self.players = list();
|
||||
self.subchans = list();
|
||||
self.linked = list();
|
||||
|
||||
self.channel_obj = channel_obj;
|
||||
self.chanid = channel_obj.id;
|
||||
|
||||
self.parent = parent_chan;
|
||||
if self.parent is not None:
|
||||
self.parent.subchans.append( self );
|
||||
|
||||
self._acl = None;
|
||||
|
||||
# Lookup unknown attributes in self.channel_obj to automatically include Murmur's fields
|
||||
def __getattr__( self, key ):
|
||||
if hasattr( self.channel_obj, key ):
|
||||
return getattr( self.channel_obj, key );
|
||||
else:
|
||||
raise AttributeError( "'%s' object has no attribute '%s'" % ( self.__class__.__name__, key ) );
|
||||
|
||||
def parent_channels( self ):
|
||||
""" Return the names of this channel's parents in the channel tree. """
|
||||
if self.parent is None or self.parent.is_server or self.parent.chanid == 0:
|
||||
return [];
|
||||
return self.parent.parent_channels() + [self.parent.name];
|
||||
|
||||
|
||||
def getACL( self ):
|
||||
""" Retrieve the ACL for this channel. """
|
||||
if not self._acl:
|
||||
self._acl = mmACL( self, self.server.ctl.getACL( self.server.srvid, self.chanid ) );
|
||||
|
||||
return self._acl;
|
||||
|
||||
acl = property( getACL, doc=getACL.__doc__ );
|
||||
|
||||
|
||||
is_server = False;
|
||||
is_channel = True;
|
||||
is_player = False;
|
||||
|
||||
|
||||
playerCount = property(
|
||||
lambda self: len( self.players ) + sum( [ chan.playerCount for chan in self.subchans ] ),
|
||||
doc="The number of players in this channel."
|
||||
);
|
||||
|
||||
id = property(
|
||||
lambda self: "channel_%d"%self.chanid,
|
||||
doc="A string ready to be used in an id property of an HTML tag."
|
||||
);
|
||||
|
||||
top_or_not_empty = property(
|
||||
lambda self: self.parent is None or self.parent.chanid == 0 or self.playerCount > 0,
|
||||
doc="True if this channel needs to be shown because it is root, a child of root, or has players."
|
||||
);
|
||||
|
||||
show = property( lambda self: settings.SHOW_EMPTY_SUBCHANS or self.top_or_not_empty );
|
||||
|
||||
def __str__( self ):
|
||||
return '<Channel "%s" (%d)>' % ( self.name, self.chanid );
|
||||
|
||||
def sort( self ):
|
||||
""" Sort my subchannels and players, and then iterate over them and sort them recursively. """
|
||||
self.subchans.sort( cmp_names );
|
||||
self.players.sort( cmp_names );
|
||||
for subc in self.subchans:
|
||||
subc.sort();
|
||||
|
||||
def visit( self, callback, lvl = 0 ):
|
||||
""" Call callback on myself, then visit my subchans, then my players. """
|
||||
callback( self, lvl );
|
||||
for subc in self.subchans:
|
||||
subc.visit( callback, lvl + 1 );
|
||||
for plr in self.players:
|
||||
plr.visit( callback, lvl + 1 );
|
||||
|
||||
|
||||
def getURL( self, for_user = None ):
|
||||
""" Create an URL to connect to this channel. The URL is of the form
|
||||
mumble://username@host:port/parentchans/self.name
|
||||
"""
|
||||
userstr = "";
|
||||
|
||||
if for_user is not None:
|
||||
userstr = "%s@" % for_user.name;
|
||||
|
||||
versionstr = "version=%d.%d.%d" % tuple(self.server.version[0:3]);
|
||||
|
||||
# create list of all my parents and myself
|
||||
chanlist = self.parent_channels() + [self.name];
|
||||
# urlencode channel names
|
||||
chanlist = [ urlquote( chan ) for chan in chanlist ];
|
||||
# create a path by joining the channel names
|
||||
chanpath = join( *chanlist );
|
||||
|
||||
if self.server.port != settings.MUMBLE_DEFAULT_PORT:
|
||||
return "mumble://%s%s:%d/%s?%s" % ( userstr, self.server.addr, self.server.port, chanpath, versionstr );
|
||||
|
||||
return "mumble://%s%s/%s?%s" % ( userstr, self.server.addr, chanpath, versionstr );
|
||||
|
||||
connecturl = property( getURL, doc="A convenience wrapper for getURL." );
|
||||
|
||||
def setDefault( self ):
|
||||
""" Make this the server's default channel. """
|
||||
self.server.defchan = self.chanid;
|
||||
self.server.save();
|
||||
|
||||
is_default = property(
|
||||
lambda self: self.server.defchan == self.chanid,
|
||||
doc="True if this channel is the server's default channel."
|
||||
);
|
||||
|
||||
def asDict( self ):
|
||||
chandata = self.channel_obj.__dict__.copy();
|
||||
chandata['players'] = [ pl.asDict() for pl in self.players ];
|
||||
chandata['subchans'] = [ sc.asDict() for sc in self.subchans ];
|
||||
return chandata;
|
||||
|
||||
|
||||
|
||||
|
||||
class mmPlayer( object ):
|
||||
""" Represents a Player in Murmur. """
|
||||
|
||||
def __init__( self, server, player_obj, player_chan ):
|
||||
self.player_obj = player_obj;
|
||||
|
||||
self.onlinesince = datetime.datetime.fromtimestamp( float( time() - player_obj.onlinesecs ) );
|
||||
self.channel = player_chan;
|
||||
self.channel.players.append( self );
|
||||
|
||||
if self.isAuthed:
|
||||
from mumble.models import MumbleUser
|
||||
try:
|
||||
self.mumbleuser = MumbleUser.objects.get( mumbleid=self.userid, server=server );
|
||||
except MumbleUser.DoesNotExist:
|
||||
self.mumbleuser = None;
|
||||
else:
|
||||
self.mumbleuser = None;
|
||||
|
||||
# Lookup unknown attributes in self.player_obj to automatically include Murmur's fields
|
||||
def __getattr__( self, key ):
|
||||
if hasattr( self.player_obj, key ):
|
||||
return getattr( self.player_obj, key );
|
||||
else:
|
||||
raise AttributeError( "'%s' object has no attribute '%s'" % ( self.__class__.__name__, key ) );
|
||||
|
||||
def __str__( self ):
|
||||
return '<Player "%s" (%d, %d)>' % ( self.name, self.session, self.userid );
|
||||
|
||||
hasComment = property(
|
||||
lambda self: hasattr( self.player_obj, "comment" ) and bool(self.player_obj.comment),
|
||||
doc="True if this player has a comment set."
|
||||
);
|
||||
|
||||
isAuthed = property(
|
||||
lambda self: self.userid != -1,
|
||||
doc="True if this player is authenticated (+A)."
|
||||
);
|
||||
|
||||
isAdmin = property(
|
||||
lambda self: self.mumbleuser and self.mumbleuser.getAdmin(),
|
||||
doc="True if this player is in the Admin group in the ACL."
|
||||
);
|
||||
|
||||
is_server = False;
|
||||
is_channel = False;
|
||||
is_player = True;
|
||||
|
||||
# kept for compatibility to mmChannel (useful for traversal funcs)
|
||||
playerCount = property( lambda self: -1, doc="Exists only for compatibility to mmChannel." );
|
||||
|
||||
id = property(
|
||||
lambda self: "player_%d"%self.session,
|
||||
doc="A string ready to be used in an id property of an HTML tag."
|
||||
);
|
||||
|
||||
def visit( self, callback, lvl = 0 ):
|
||||
""" Call callback on myself. """
|
||||
callback( self, lvl );
|
||||
|
||||
def asDict( self ):
|
||||
pldata = self.player_obj.__dict__.copy();
|
||||
|
||||
if self.mumbleuser:
|
||||
if self.mumbleuser.hasTexture():
|
||||
pldata['texture'] = self.mumbleuser.textureUrl;
|
||||
|
||||
return pldata;
|
||||
|
||||
|
||||
|
||||
class mmACL( object ):
|
||||
""" Represents an ACL for a certain channel. """
|
||||
|
||||
def __init__( self, channel, acl_obj ):
|
||||
self.channel = channel;
|
||||
self.acls, self.groups, self.inherit = acl_obj;
|
||||
|
||||
self.groups_dict = {};
|
||||
|
||||
for group in self.groups:
|
||||
self.groups_dict[ group.name ] = group;
|
||||
|
||||
def group_has_member( self, name, userid ):
|
||||
""" Checks if the given userid is a member of the given group in this channel. """
|
||||
if name not in self.groups_dict:
|
||||
raise ReferenceError( "No such group '%s'" % name );
|
||||
|
||||
return userid in self.groups_dict[name].add or userid in self.groups_dict[name].members;
|
||||
|
||||
def group_add_member( self, name, userid ):
|
||||
""" Make sure this userid is a member of the group in this channel (and subs). """
|
||||
if name not in self.groups_dict:
|
||||
raise ReferenceError( "No such group '%s'" % name );
|
||||
|
||||
group = self.groups_dict[name];
|
||||
|
||||
# if neither inherited nor to be added, add
|
||||
if userid not in group.members and userid not in group.add:
|
||||
group.add.append( userid );
|
||||
|
||||
# if to be removed, unremove
|
||||
if userid in group.remove:
|
||||
group.remove.remove( userid );
|
||||
|
||||
def group_remove_member( self, name, userid ):
|
||||
""" Make sure this userid is NOT a member of the group in this channel (and subs). """
|
||||
if name not in self.groups_dict:
|
||||
raise ReferenceError( "No such group '%s'" % name );
|
||||
|
||||
group = self.groups_dict[name];
|
||||
|
||||
# if added here, unadd
|
||||
if userid in group.add:
|
||||
group.add.remove( userid );
|
||||
# if member and not in remove, add to remove
|
||||
elif userid in group.members and userid not in group.remove:
|
||||
group.remove.append( userid );
|
||||
|
||||
def save( self ):
|
||||
""" Send this ACL to Murmur. """
|
||||
return self.channel.server.ctl.setACL(
|
||||
self.channel.server.srvid,
|
||||
self.channel.chanid,
|
||||
self.acls, self.groups, self.inherit
|
||||
);
|
||||
|
||||
|
||||
|
||||
565
mumble/models.py
Normal file
565
mumble/models.py
Normal file
@@ -0,0 +1,565 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
import socket
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import User
|
||||
from django.db import models
|
||||
from django.db.models import signals
|
||||
from django.conf import settings
|
||||
|
||||
from mumble.mmobjects import mmChannel, mmPlayer
|
||||
from mumble.mctl import MumbleCtlBase
|
||||
|
||||
|
||||
def mk_config_property( field, doc="" ):
|
||||
""" Create a property for the given config field. """
|
||||
|
||||
def get_field( self ):
|
||||
if self.id is not None:
|
||||
return self.getConf( field )
|
||||
else:
|
||||
return None
|
||||
|
||||
def set_field( self, value ):
|
||||
self.setConf( field, value )
|
||||
|
||||
return property( get_field, set_field, doc=doc )
|
||||
|
||||
|
||||
class Mumble( models.Model ):
|
||||
""" Represents a Murmur server instance.
|
||||
|
||||
All configurable settings are represented by a field in this model. To change the
|
||||
settings, just update the appropriate field and call the save() method.
|
||||
|
||||
To set up a new server instance, instanciate this Model. The first field you should
|
||||
define is the "dbus" field, which tells the connector subsystem how to connect to
|
||||
the Murmurd master process. Set this to the appropriate DBus service name or the
|
||||
Ice proxy string.
|
||||
|
||||
When an instance of this model is deleted, the according server instance will be
|
||||
deleted as well.
|
||||
"""
|
||||
|
||||
name = models.CharField( _('Server Name'), max_length = 200 );
|
||||
dbus = models.CharField( _('DBus or ICE base'), max_length = 200, default = settings.DEFAULT_CONN, help_text=_(
|
||||
"Examples: 'net.sourceforge.mumble.murmur' for DBus or 'Meta:tcp -h 127.0.0.1 -p 6502' for Ice.") );
|
||||
srvid = models.IntegerField( _('Server ID'), editable = False );
|
||||
addr = models.CharField( _('Server Address'), max_length = 200, help_text=_(
|
||||
"Hostname or IP address to bind to. You should use a hostname here, because it will appear on the "
|
||||
"global server list.") );
|
||||
port = models.IntegerField( _('Server Port'), default=settings.MUMBLE_DEFAULT_PORT, help_text=_(
|
||||
"Port number to bind to. Use -1 to auto assign one.") );
|
||||
|
||||
|
||||
supw = property( lambda self: '',
|
||||
lambda self, value: self.ctl.setSuperUserPassword( self.srvid, value ),
|
||||
doc='Superuser Password'
|
||||
)
|
||||
|
||||
url = mk_config_property( "registerurl", "Website URL" )
|
||||
motd = mk_config_property( "welcometext", "Welcome Message" )
|
||||
passwd = mk_config_property( "password", "Server Password" )
|
||||
users = mk_config_property( "users", "Max. Users" )
|
||||
bwidth = mk_config_property( "bandwidth", "Bandwidth [Bps]" )
|
||||
sslcrt = mk_config_property( "certificate", "SSL Certificate" )
|
||||
sslkey = mk_config_property( "key", "SSL Key" )
|
||||
player = mk_config_property( "username", "Player name regex" )
|
||||
channel = mk_config_property( "channelname", "Channel name regex" )
|
||||
defchan = mk_config_property( "defaultchannel", "Default channel" )
|
||||
|
||||
obfsc = property(
|
||||
lambda self: ( self.getConf( "obfuscate" ) == "true" ) if self.id is not None else None,
|
||||
lambda self, value: self.setConf( "obfuscate", str(value).lower() ),
|
||||
doc="IP Obfuscation"
|
||||
)
|
||||
|
||||
def getBooted( self ):
|
||||
if self.id is not None:
|
||||
return self.ctl.isBooted( self.srvid );
|
||||
else:
|
||||
return False
|
||||
|
||||
def setBooted( self, value ):
|
||||
if value != self.getBooted():
|
||||
if value:
|
||||
self.ctl.start( self.srvid );
|
||||
else:
|
||||
self.ctl.stop( self.srvid );
|
||||
|
||||
booted = property( getBooted, setBooted, doc="Boot Server" )
|
||||
|
||||
class Meta:
|
||||
unique_together = ( ( 'dbus', 'srvid' ), ( 'addr', 'port' ), );
|
||||
verbose_name = _('Server instance');
|
||||
verbose_name_plural = _('Server instances');
|
||||
|
||||
def __unicode__( self ):
|
||||
if not self.id:
|
||||
return u'Murmur "%s" (NOT YET CREATED)' % self.name;
|
||||
return u'Murmur "%s" (%d)' % ( self.name, self.srvid );
|
||||
|
||||
def save( self, dontConfigureMurmur=False ):
|
||||
""" Save the options configured in this model instance not only to Django's database,
|
||||
but to Murmur as well.
|
||||
"""
|
||||
if dontConfigureMurmur:
|
||||
# skip murmur configuration, e.g. because we're inserting models for existing servers.
|
||||
return models.Model.save( self );
|
||||
|
||||
# check if this server already exists, if not call newServer and set my srvid first
|
||||
|
||||
if self.id is None:
|
||||
self.srvid = self.ctl.newServer();
|
||||
|
||||
if self.port == -1:
|
||||
self.port = max( [ rec['port'] for rec in Mumble.objects.values('port') ] ) + 1;
|
||||
|
||||
if self.port < 1 or self.port >= 2**16:
|
||||
raise ValueError( _("Port number %(portno)d is not within the allowed range %(minrange)d - %(maxrange)d") % {
|
||||
'portno': self.port,
|
||||
'minrange': 1,
|
||||
'maxrange': 2**16,
|
||||
});
|
||||
|
||||
self.ctl.setConf( self.srvid, 'host', socket.gethostbyname( self.addr ) );
|
||||
self.ctl.setConf( self.srvid, 'port', str(self.port) );
|
||||
self.ctl.setConf( self.srvid, 'registername', self.name );
|
||||
self.ctl.setConf( self.srvid, 'registerurl', self.url );
|
||||
|
||||
# registerHostname needs to take the port no into account
|
||||
if self.port and self.port != settings.MUMBLE_DEFAULT_PORT:
|
||||
self.ctl.setConf( self.srvid, 'registerhostname', "%s:%d" % ( self.addr, self.port ) );
|
||||
else:
|
||||
self.ctl.setConf( self.srvid, 'registerhostname', self.addr );
|
||||
|
||||
if self.supw:
|
||||
self.ctl.setSuperUserPassword( self.srvid, self.supw );
|
||||
self.supw = '';
|
||||
|
||||
if self.booted != self.ctl.isBooted( self.srvid ):
|
||||
if self.booted:
|
||||
self.ctl.start( self.srvid );
|
||||
else:
|
||||
self.ctl.stop( self.srvid );
|
||||
|
||||
# Now allow django to save the record set
|
||||
return models.Model.save( self );
|
||||
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
models.Model.__init__( self, *args, **kwargs );
|
||||
self._ctl = None;
|
||||
self._channels = None;
|
||||
self._rootchan = None;
|
||||
|
||||
|
||||
users_regged = property( lambda self: self.mumbleuser_set.count(), doc="Number of registered users." );
|
||||
users_online = property( lambda self: len(self.ctl.getPlayers(self.srvid)), doc="Number of online users." );
|
||||
channel_cnt = property( lambda self: len(self.ctl.getChannels(self.srvid)), doc="Number of channels." );
|
||||
is_public = property( lambda self: self.passwd == '',
|
||||
doc="False if a password is needed to join this server." );
|
||||
|
||||
is_server = True;
|
||||
is_channel = False;
|
||||
is_player = False;
|
||||
|
||||
|
||||
# Ctl instantiation
|
||||
def getCtl( self ):
|
||||
""" Instantiate and return a MumbleCtl object for this server.
|
||||
|
||||
Only one instance will be created, and reused on subsequent calls.
|
||||
"""
|
||||
if not self._ctl:
|
||||
self._ctl = MumbleCtlBase.newInstance( self.dbus, settings.SLICE );
|
||||
return self._ctl;
|
||||
|
||||
ctl = property( getCtl, doc="Get a Control object for this server. The ctl is cached for later reuse." );
|
||||
|
||||
|
||||
def getConf( self, field ):
|
||||
return self.ctl.getConf( self.srvid, field )
|
||||
|
||||
def setConf( self, field, value ):
|
||||
return self.ctl.setConf( self.srvid, field, value )
|
||||
|
||||
def configureFromMurmur( self ):
|
||||
default = self.ctl.getDefaultConf();
|
||||
conf = self.ctl.getAllConf( self.srvid );
|
||||
|
||||
def find_in_dicts( keys, valueIfNotFound=None ):
|
||||
if not isinstance( keys, tuple ):
|
||||
keys = ( keys, );
|
||||
|
||||
for keyword in keys:
|
||||
if keyword in conf:
|
||||
return conf[keyword];
|
||||
|
||||
for keyword in keys:
|
||||
keyword = keyword.lower();
|
||||
if keyword in default:
|
||||
return default[keyword];
|
||||
|
||||
return valueIfNotFound;
|
||||
|
||||
servername = find_in_dicts( "registername", "noname" );
|
||||
if not servername:
|
||||
# RegistrationName was found in the dicts, but is an empty string
|
||||
servername = "noname";
|
||||
|
||||
addr = find_in_dicts( ( "registerhostname", "host" ), "0.0.0.0" );
|
||||
if addr.find( ':' ) != -1:
|
||||
# The addr is a hostname which actually contains a port number, but we already got that from
|
||||
# the port field, so we can simply drop it.
|
||||
addr = addr.split(':')[0];
|
||||
|
||||
self.name = servername;
|
||||
self.addr = addr;
|
||||
self.port = find_in_dicts( "port" );
|
||||
|
||||
self.save( dontConfigureMurmur=True );
|
||||
|
||||
|
||||
def readUsersFromMurmur( self, verbose=0 ):
|
||||
if not self.booted:
|
||||
raise SystemError( "This murmur instance is not currently running, can't sync." );
|
||||
|
||||
players = self.ctl.getRegisteredPlayers(self.srvid);
|
||||
|
||||
for idx in players:
|
||||
playerdata = players[idx];
|
||||
|
||||
if playerdata.userid == 0: # Skip SuperUsers
|
||||
continue;
|
||||
if verbose > 1:
|
||||
print "Checking Player with id %d and name '%s'." % ( playerdata.userid, playerdata.name );
|
||||
|
||||
try:
|
||||
playerinstance = MumbleUser.objects.get( server=self, mumbleid=playerdata.userid );
|
||||
|
||||
except MumbleUser.DoesNotExist:
|
||||
if verbose:
|
||||
print 'Found new Player "%s".' % playerdata.name;
|
||||
|
||||
playerinstance = MumbleUser(
|
||||
mumbleid = playerdata.userid,
|
||||
name = playerdata.name,
|
||||
password = '',
|
||||
server = self,
|
||||
owner = None
|
||||
);
|
||||
|
||||
else:
|
||||
if verbose > 1:
|
||||
print "This player is already listed in the database.";
|
||||
|
||||
playerinstance.name = playerdata.name;
|
||||
|
||||
playerinstance.save( dontConfigureMurmur=True );
|
||||
|
||||
|
||||
def isUserAdmin( self, user ):
|
||||
""" Determine if the given user is an admin on this server. """
|
||||
if user.is_authenticated():
|
||||
try:
|
||||
return self.mumbleuser_set.get( owner=user ).getAdmin();
|
||||
except MumbleUser.DoesNotExist:
|
||||
return False;
|
||||
return False;
|
||||
|
||||
|
||||
# Deletion handler
|
||||
def deleteServer( self ):
|
||||
""" Delete this server instance from Murmur. """
|
||||
self.ctl.deleteServer(self.srvid)
|
||||
|
||||
@staticmethod
|
||||
def pre_delete_listener( **kwargs ):
|
||||
kwargs['instance'].deleteServer();
|
||||
|
||||
|
||||
# Channel list
|
||||
def getChannels( self ):
|
||||
""" Query the channels from Murmur and create a tree structure.
|
||||
|
||||
Again, this will only be done for the first call to this function. Subsequent
|
||||
calls will simply return the list created last time.
|
||||
"""
|
||||
if self._channels is None:
|
||||
self._channels = {};
|
||||
chanlist = self.ctl.getChannels(self.srvid).values();
|
||||
links = {};
|
||||
|
||||
# sometimes, ICE seems to return the Channel list in a weird order.
|
||||
# itercount prevents infinite loops.
|
||||
itercount = 0;
|
||||
maxiter = len(chanlist) * 3;
|
||||
while len(chanlist) and itercount < maxiter:
|
||||
itercount += 1;
|
||||
for theChan in chanlist:
|
||||
# Channels - Fields: 0 = ID, 1 = Name, 2 = Parent-ID, 3 = Links
|
||||
if( theChan.parent == -1 ):
|
||||
# No parent
|
||||
self._channels[theChan.id] = mmChannel( self, theChan );
|
||||
elif theChan.parent in self.channels:
|
||||
# parent already known
|
||||
self._channels[theChan.id] = mmChannel( self, theChan, self.channels[theChan.parent] );
|
||||
else:
|
||||
continue;
|
||||
|
||||
chanlist.remove( theChan );
|
||||
|
||||
self._channels[theChan.id].serverId = self.id;
|
||||
|
||||
# process links - if the linked channels are known, link; else save their ids to link later
|
||||
for linked in theChan.links:
|
||||
if linked in self._channels:
|
||||
self._channels[theChan.id].linked.append( self._channels[linked] );
|
||||
else:
|
||||
if linked not in links:
|
||||
links[linked] = list();
|
||||
links[linked].append( self._channels[theChan.id] );
|
||||
|
||||
# check if earlier round trips saved channel ids to be linked to the current channel
|
||||
if theChan.id in links:
|
||||
for targetChan in links[theChan.id]:
|
||||
targetChan.linked.append( self._channels[theChan.id] );
|
||||
|
||||
self._channels[0].name = self.name;
|
||||
|
||||
self.players = {};
|
||||
for thePlayer in self.ctl.getPlayers(self.srvid).values():
|
||||
# Players - Fields: 0 = UserID, 6 = ChannelID
|
||||
self.players[ thePlayer.session ] = mmPlayer( self, thePlayer, self._channels[ thePlayer.channel ] );
|
||||
|
||||
self._channels[0].sort();
|
||||
|
||||
return self._channels;
|
||||
|
||||
channels = property( getChannels, doc="A convenience wrapper for getChannels()." );
|
||||
rootchan = property( lambda self: self.channels[0], doc="A convenience wrapper for getChannels()[0]." );
|
||||
|
||||
def getURL( self, forUser = None ):
|
||||
""" Create an URL of the form mumble://username@host:port/ for this server. """
|
||||
userstr = "";
|
||||
if forUser is not None:
|
||||
userstr = "%s@" % forUser.name;
|
||||
|
||||
versionstr = "version=%d.%d.%d" % tuple(self.version[0:3]);
|
||||
|
||||
if self.port != settings.MUMBLE_DEFAULT_PORT:
|
||||
return "mumble://%s%s:%d/?%s" % ( userstr, self.addr, self.port, versionstr );
|
||||
|
||||
return "mumble://%s%s/?%s" % ( userstr, self.addr, versionstr );
|
||||
|
||||
connecturl = property( getURL, doc="A convenience wrapper for getURL()." );
|
||||
|
||||
version = property( lambda self: self.ctl.getVersion(), doc="The version of Murmur." );
|
||||
|
||||
def asDict( self ):
|
||||
return { 'name': self.name,
|
||||
'id': self.id,
|
||||
'root': self.rootchan.asDict()
|
||||
};
|
||||
|
||||
|
||||
|
||||
class MumbleUser( models.Model ):
|
||||
""" Represents a User account in Murmur.
|
||||
|
||||
To change an account, simply set the according field in this model and call the save()
|
||||
method to update the account in Murmur and in Django's database. Note that, once saved
|
||||
for the first time, the server field must not be changed. Attempting to do this will
|
||||
result in an AttributeError. To move an account to a new server, recreate it on the
|
||||
new server and delete the old model.
|
||||
|
||||
When you delete an instance of this model, the according user account will be deleted
|
||||
in Murmur as well, after revoking the user's admin privileges.
|
||||
"""
|
||||
|
||||
mumbleid = models.IntegerField( _('Mumble player_id'), editable = False, default = -1 );
|
||||
name = models.CharField( _('User name and Login'), max_length = 200 );
|
||||
password = models.CharField( _('Login password'), max_length = 200, blank=True );
|
||||
server = models.ForeignKey( Mumble, verbose_name=_('Server instance'), related_name="mumbleuser_set" );
|
||||
owner = models.ForeignKey( User, verbose_name=_('Account owner'), related_name="mumbleuser_set", null=True, blank=True );
|
||||
|
||||
class Meta:
|
||||
unique_together = ( ( 'server', 'owner' ), ( 'server', 'mumbleid' ) );
|
||||
verbose_name = _( 'User account' );
|
||||
verbose_name_plural = _( 'User accounts' );
|
||||
|
||||
is_server = False;
|
||||
is_channel = False;
|
||||
is_player = True;
|
||||
|
||||
def __unicode__( self ):
|
||||
return _("Mumble user %(mu)s on %(srv)s owned by Django user %(du)s") % {
|
||||
'mu': self.name,
|
||||
'srv': self.server,
|
||||
'du': self.owner
|
||||
};
|
||||
|
||||
def save( self, dontConfigureMurmur=False ):
|
||||
""" Save the settings in this model to Murmur. """
|
||||
if dontConfigureMurmur:
|
||||
# skip murmur configuration, e.g. because we're inserting models for existing players.
|
||||
return models.Model.save( self );
|
||||
|
||||
# Before the record set is saved, update Murmur via controller.
|
||||
ctl = self.server.ctl;
|
||||
|
||||
if self.owner:
|
||||
email = self.owner.email;
|
||||
else:
|
||||
email = settings.DEFAULT_FROM_EMAIL;
|
||||
|
||||
if self.id is None:
|
||||
# This is a new user record, so Murmur doesn't know about it yet
|
||||
if len( ctl.getRegisteredPlayers( self.server.srvid, self.name ) ) > 0:
|
||||
raise ValueError( _( "Another player already registered that name." ) );
|
||||
if not self.password:
|
||||
raise ValueError( _( "Cannot register player without a password!" ) );
|
||||
|
||||
self.mumbleid = ctl.registerPlayer( self.server.srvid, self.name, email, self.password );
|
||||
|
||||
# Update user's registration
|
||||
elif self.password:
|
||||
ctl.setRegistration(
|
||||
self.server.srvid,
|
||||
self.mumbleid,
|
||||
self.name,
|
||||
email,
|
||||
self.password
|
||||
);
|
||||
|
||||
# Don't save the users' passwords, we don't need them anyway
|
||||
self.password = '';
|
||||
|
||||
# Now allow django to save the record set
|
||||
return models.Model.save( self );
|
||||
|
||||
def __init__( self, *args, **kwargs ):
|
||||
models.Model.__init__( self, *args, **kwargs );
|
||||
self._registration = None;
|
||||
|
||||
# Admin handlers
|
||||
def getAdmin( self ):
|
||||
""" Get ACL of root Channel, get the admin group and see if this user is in it. """
|
||||
return self.server.rootchan.acl.group_has_member( "admin", self.mumbleid );
|
||||
|
||||
def setAdmin( self, value ):
|
||||
""" Set or revoke this user's membership in the admin group on the root channel. """
|
||||
if value:
|
||||
self.server.rootchan.acl.group_add_member( "admin", self.mumbleid );
|
||||
else:
|
||||
self.server.rootchan.acl.group_remove_member( "admin", self.mumbleid );
|
||||
self.server.rootchan.acl.save();
|
||||
return value;
|
||||
|
||||
aclAdmin = property( getAdmin, setAdmin, doc="Wrapper around getAdmin/setAdmin (not a database field like isAdmin)" );
|
||||
|
||||
# Registration fetching
|
||||
def getRegistration( self ):
|
||||
""" Retrieve a user's registration from Murmur as a dict. """
|
||||
if not self._registration:
|
||||
self._registration = self.server.ctl.getRegistration( self.server.srvid, self.mumbleid );
|
||||
return self._registration;
|
||||
|
||||
registration = property( getRegistration, doc=getRegistration.__doc__ );
|
||||
|
||||
def getComment( self ):
|
||||
""" Retrieve a user's comment, if any. """
|
||||
if "comment" in self.registration:
|
||||
return self.registration["comment"];
|
||||
else:
|
||||
return None;
|
||||
|
||||
comment = property( getComment, doc=getComment.__doc__ );
|
||||
|
||||
def getHash( self ):
|
||||
""" Retrieve a user's hash, if any. """
|
||||
if "hash" in self.registration:
|
||||
return self.registration["hash"];
|
||||
else:
|
||||
return None;
|
||||
|
||||
hash = property( getHash, doc=getHash.__doc__ );
|
||||
|
||||
# Texture handlers
|
||||
|
||||
def getTexture( self ):
|
||||
""" Get the user texture as a PIL Image. """
|
||||
return self.server.ctl.getTexture(self.server.srvid, self.mumbleid);
|
||||
|
||||
def setTexture( self, infile ):
|
||||
""" Read an image from the infile and install it as the user's texture. """
|
||||
self.server.ctl.setTexture(self.server.srvid, self.mumbleid, infile)
|
||||
|
||||
texture = property( getTexture, setTexture,
|
||||
doc="Get the texture as a PIL Image or read from a file (pass the path)."
|
||||
);
|
||||
|
||||
def hasTexture( self ):
|
||||
""" Check if this user has a texture set. """
|
||||
try:
|
||||
self.getTexture();
|
||||
except ValueError:
|
||||
return False;
|
||||
else:
|
||||
return True;
|
||||
|
||||
def getTextureUrl( self ):
|
||||
""" Get a URL under which the texture can be retrieved. """
|
||||
from views import showTexture
|
||||
from django.core.urlresolvers import reverse
|
||||
return reverse( showTexture, kwargs={ 'server': self.server.id, 'userid': self.id } );
|
||||
|
||||
textureUrl = property( getTextureUrl, doc=getTextureUrl.__doc__ );
|
||||
|
||||
# Deletion handler
|
||||
|
||||
@staticmethod
|
||||
def pre_delete_listener( **kwargs ):
|
||||
kwargs['instance'].unregister();
|
||||
|
||||
def unregister( self ):
|
||||
""" Delete this user account from Murmur. """
|
||||
if self.getAdmin():
|
||||
self.setAdmin( False );
|
||||
self.server.ctl.unregisterPlayer(self.server.srvid, self.mumbleid)
|
||||
|
||||
|
||||
# "server" field protection
|
||||
|
||||
def __setattr__( self, name, value ):
|
||||
if name == 'server':
|
||||
if self.id is not None and self.server != value:
|
||||
raise AttributeError( _( "This field must not be updated once the record has been saved." ) );
|
||||
|
||||
models.Model.__setattr__( self, name, value );
|
||||
|
||||
|
||||
|
||||
|
||||
signals.pre_delete.connect( Mumble.pre_delete_listener, sender=Mumble );
|
||||
signals.pre_delete.connect( MumbleUser.pre_delete_listener, sender=MumbleUser );
|
||||
|
||||
|
||||
|
||||
|
||||
244
mumble/murmurenvutils.py
Normal file
244
mumble/murmurenvutils.py
Normal file
@@ -0,0 +1,244 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
import os, subprocess, signal
|
||||
from select import select
|
||||
from os.path import join, exists
|
||||
from shutil import copyfile
|
||||
|
||||
from django.conf import settings
|
||||
|
||||
from utils import ObjectInfo
|
||||
|
||||
def get_available_versions():
|
||||
""" Return murmur versions installed inside the LAB_DIR. """
|
||||
dirs = os.listdir( settings.TEST_MURMUR_LAB_DIR );
|
||||
dirs.sort();
|
||||
return dirs;
|
||||
|
||||
|
||||
def run_callback( version, callback, *args, **kwargs ):
|
||||
""" Initialize the database and run murmur, then call the callback.
|
||||
After the callback has returned, kill murmur.
|
||||
|
||||
The callback will be passed the Popen object that wraps murmur,
|
||||
and any arguments that were passed to run_callback.
|
||||
|
||||
If the callback raises an exception, murmur will still be properly
|
||||
shutdown and the exception will be reraised.
|
||||
|
||||
The callback can either return an arbitrary value, or a tuple.
|
||||
If it returns a tuple, it must be of the form:
|
||||
|
||||
( <any> intended_return_value, <bool> call_update_dbase )
|
||||
|
||||
That means: If the second value evaluates to True, update_dbase
|
||||
will be called; the first value will be returned by run_callback.
|
||||
|
||||
If the callback returns anything other than a tuple, that value
|
||||
will be returned directly.
|
||||
|
||||
So, If run_callback should return a tuple, you will need to return
|
||||
the tuple form mentioned above in the callback, and put your tuple
|
||||
into the first parameter.
|
||||
"""
|
||||
|
||||
murmur_root = join( settings.TEST_MURMUR_LAB_DIR, version );
|
||||
if not exists( murmur_root ):
|
||||
raise EnvironmentError( "This version could not be found: '%s' does not exist!" % murmur_root );
|
||||
|
||||
init_dbase( version );
|
||||
|
||||
process = run_murmur( version );
|
||||
|
||||
try:
|
||||
result = callback( process, *args, **kwargs );
|
||||
if type(result) == tuple:
|
||||
if result[1]:
|
||||
update_dbase( version );
|
||||
return result[0];
|
||||
else:
|
||||
return result;
|
||||
finally:
|
||||
kill_murmur( process );
|
||||
|
||||
|
||||
def init_dbase( version ):
|
||||
""" Initialize Murmur's database by copying the one from FILES_DIR. """
|
||||
dbasefile = join( settings.TEST_MURMUR_FILES_DIR, "murmur-%s.db3" % version );
|
||||
if not exists( dbasefile ):
|
||||
raise EnvironmentError( "This version could not be found: '%s' does not exist!" % dbasefile );
|
||||
murmurfile = join( settings.TEST_MURMUR_LAB_DIR, version, "murmur.sqlite" );
|
||||
copyfile( dbasefile, murmurfile );
|
||||
|
||||
|
||||
def update_dbase( version ):
|
||||
""" Copy Murmur's database to FILES_DIR (the inverse of init_dbase). """
|
||||
murmurfile = join( settings.TEST_MURMUR_LAB_DIR, version, "murmur.sqlite" );
|
||||
if not exists( murmurfile ):
|
||||
raise EnvironmentError( "Murmur's database could not be found: '%s' does not exist!" % murmurfile );
|
||||
dbasefile = join( settings.TEST_MURMUR_FILES_DIR, "murmur-%s.db3" % version );
|
||||
copyfile( murmurfile, dbasefile );
|
||||
|
||||
|
||||
def run_murmur( version ):
|
||||
""" Run the given Murmur version as a subprocess.
|
||||
|
||||
Either returns a Popen object or raises an EnvironmentError.
|
||||
"""
|
||||
|
||||
murmur_root = join( settings.TEST_MURMUR_LAB_DIR, version );
|
||||
if not exists( murmur_root ):
|
||||
raise EnvironmentError( "This version could not be found: '%s' does not exist!" % murmur_root );
|
||||
|
||||
binary_candidates = ( 'murmur.64', 'murmur.x86', 'murmurd' );
|
||||
|
||||
for binname in binary_candidates:
|
||||
if exists( join( murmur_root, binname ) ):
|
||||
process = subprocess.Popen(
|
||||
( join( murmur_root, binname ), '-fg' ),
|
||||
stdin=None, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
||||
cwd=murmur_root
|
||||
);
|
||||
|
||||
# Check capabilities by waiting for certain lines to show up.
|
||||
capa = ObjectInfo( has_dbus=False, has_ice=False, has_instance=False, has_users=False );
|
||||
|
||||
def canRead( self, timeout=1 ):
|
||||
rdy_read, rdy_write, rdy_other = select( [self.stdout], [], [], timeout );
|
||||
return self.stdout in rdy_read;
|
||||
|
||||
setattr(subprocess.Popen, 'canRead', canRead)
|
||||
|
||||
while process.canRead(0.5):
|
||||
line = process.stdout.readline();
|
||||
#print "read line:", line
|
||||
if line == 'DBus registration succeeded\n':
|
||||
capa.has_dbus = True;
|
||||
elif line == 'MurmurIce: Endpoint "tcp -h 127.0.0.1 -p 6502" running\n':
|
||||
capa.has_ice = True;
|
||||
elif line == '1 => Server listening on 0.0.0.0:64738\n':
|
||||
capa.has_instance = True;
|
||||
elif "> Authenticated\n" in line:
|
||||
capa.has_users = True;
|
||||
|
||||
process.capabilities = capa;
|
||||
|
||||
return process;
|
||||
|
||||
raise EnvironmentError( "Murmur binary not found. (Tried %s)" % unicode(binary_candidates) );
|
||||
|
||||
|
||||
def wait_for_user( process, timeout=1 ):
|
||||
""" Wait for a user to connect. This call will consume any output from murmur
|
||||
until a line indicating a user's attempt to connect has been found.
|
||||
|
||||
The timeout parameter specifies how long (in seconds) to wait for input.
|
||||
It defaults to 1 second. If you set this to 0 it will return at the end
|
||||
of input (and thereby tell you if a player has already connected). If
|
||||
you set this to None, the call will block until a player has connected.
|
||||
|
||||
Returns True if a user has connected before the timeout has been hit,
|
||||
False otherwise.
|
||||
"""
|
||||
while process.canRead( timeout ):
|
||||
line = process.stdout.readline();
|
||||
if "> Authenticated\n" in line:
|
||||
process.capabilities.has_users = True;
|
||||
return True;
|
||||
return False;
|
||||
|
||||
|
||||
def kill_murmur( process ):
|
||||
""" Send a sigterm to the given process. """
|
||||
return os.kill( process.pid, signal.SIGTERM );
|
||||
|
||||
|
||||
class MumbleCommandWrapper_noargs( object ):
|
||||
""" Mixin used to run a standard Django command inside MurmurEnvUtils.
|
||||
|
||||
To modify a standard Django command for MEU, you will need to create
|
||||
a new command and derive its Command class from the wrapper, and the
|
||||
Command class of the original command:
|
||||
|
||||
from django.core.management.commands.shell import Command as ShellCommand
|
||||
from mumble.murmurenvutils import MumbleCommandWrapper
|
||||
|
||||
class Command( MumbleCommandWrapper, ShellCommand ):
|
||||
pass
|
||||
|
||||
That will run the original command, after the user has had the chance to
|
||||
select the version of Murmur to run.
|
||||
"""
|
||||
|
||||
def _choose_version( self ):
|
||||
print "Choose version:";
|
||||
|
||||
vv = get_available_versions();
|
||||
for idx in range(len(vv)):
|
||||
print " #%d %s" % ( idx, vv[idx] );
|
||||
|
||||
chosen = int( raw_input("#> ") );
|
||||
|
||||
return vv[chosen];
|
||||
|
||||
def handle_noargs( self, **options ):
|
||||
self.origOpts = options;
|
||||
|
||||
run_callback( self._choose_version(), self.runOrig );
|
||||
|
||||
def runOrig( self, proc ):
|
||||
super( MumbleCommandWrapper_noargs, self ).handle_noargs( **self.origOpts );
|
||||
|
||||
|
||||
class MumbleCommandWrapper( object ):
|
||||
""" Mixin used to run a standard Django command inside MurmurEnvUtils.
|
||||
|
||||
To modify a standard Django command for MEU, you will need to create
|
||||
a new command and derive its Command class from the wrapper, and the
|
||||
Command class of the original command:
|
||||
|
||||
from django.core.management.commands.shell import Command as ShellCommand
|
||||
from mumble.murmurenvutils import MumbleCommandWrapper
|
||||
|
||||
class Command( MumbleCommandWrapper, ShellCommand ):
|
||||
pass
|
||||
|
||||
That will run the original command, after the user has had the chance to
|
||||
select the version of Murmur to run.
|
||||
"""
|
||||
|
||||
def _choose_version( self ):
|
||||
print "Choose version:";
|
||||
|
||||
vv = get_available_versions();
|
||||
for idx in range(len(vv)):
|
||||
print " #%d %s" % ( idx, vv[idx] );
|
||||
|
||||
chosen = int( raw_input("#> ") );
|
||||
|
||||
return vv[chosen];
|
||||
|
||||
def handle( self, *args, **options ):
|
||||
self.origArgs = args;
|
||||
self.origOpts = options;
|
||||
|
||||
run_callback( self._choose_version(), self.runOrig );
|
||||
|
||||
def runOrig( self, proc ):
|
||||
super( MumbleCommandWrapper, self ).handle( *self.origArgs, **self.origOpts );
|
||||
|
||||
|
||||
30
mumble/utils.py
Normal file
30
mumble/utils.py
Normal file
@@ -0,0 +1,30 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
|
||||
"""
|
||||
* Copyright © 2009-2010, Michael "Svedrin" Ziegler <diese-addy@funzt-halt.net>
|
||||
*
|
||||
* Mumble-Django is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This package is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
"""
|
||||
|
||||
class ObjectInfo( object ):
|
||||
""" Wraps arbitrary information to be easily accessed. """
|
||||
|
||||
def __init__( self, **kwargs ):
|
||||
self.__dict__ = kwargs;
|
||||
|
||||
def __str__( self ):
|
||||
return unicode( self );
|
||||
|
||||
def __repr__( self ):
|
||||
return unicode( self );
|
||||
|
||||
def __unicode__( self ):
|
||||
return unicode( self.__dict__ );
|
||||
@@ -88,6 +88,7 @@ INSTALLED_APPS = (
|
||||
'registration',
|
||||
'eve_proxy',
|
||||
'eve_api',
|
||||
'mumble',
|
||||
'sso',
|
||||
)
|
||||
|
||||
@@ -109,3 +110,9 @@ JABBER_GROUP = 'dreddit'
|
||||
|
||||
# Use sudo?
|
||||
JABBER_SUDO = True
|
||||
|
||||
### Mumble Service Settings
|
||||
|
||||
DEFAULT_CONN = 'Meta:tcp -h 127.0.0.1 -p 6502'
|
||||
MUMBLE_DEFAULT_PORT = 64738
|
||||
SLICE = '/usr/share/slice/Murmur.ice'
|
||||
|
||||
Reference in New Issue
Block a user