Imported a cut down mumble app

This commit is contained in:
2010-02-26 00:15:53 +00:00
parent bfd7f95421
commit 59ec92b2d7
17 changed files with 2568 additions and 0 deletions

330
mumble/MumbleCtlDbus.py Normal file
View 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
View 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
View 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
View 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 );

View 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 );

View 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.
"""

View 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 ]";

View 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

View 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

View 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

View 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
View 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
View 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
View 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
View 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
View 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__ );

View File

@@ -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'