From 5a6747c83a663b33a9d0fbf6f32641e3f10c98d6 Mon Sep 17 00:00:00 2001 From: Michael Ziegler Date: Sun, 2 May 2010 13:17:24 +0200 Subject: [PATCH] a few code style fixes --- muco.py | 563 ++++--- mumble-django.wsgi | 2 +- munin.py | 73 +- pyweb/manage.py | 2 +- pyweb/mucli.py | 239 +-- pyweb/mumble/MumbleCtlDbus.py | 595 +++---- pyweb/mumble/MumbleCtlIce.py | 1117 ++++++------- pyweb/mumble/__init__.py | 39 +- pyweb/mumble/admin.py | 231 +-- pyweb/mumble/forms.py | 547 +++--- pyweb/mumble/management/__init__.py | 43 +- pyweb/mumble/management/commands/__init__.py | 1 + pyweb/mumble/management/commands/checkenv.py | 345 ++-- pyweb/mumble/management/commands/getslice.py | 71 +- .../mumble/management/commands/mmrunserver.py | 7 +- pyweb/mumble/management/commands/mmshell.py | 7 +- pyweb/mumble/management/commands/mmsyncdb.py | 7 +- pyweb/mumble/management/server_detect.py | 279 ++-- pyweb/mumble/management/update_schema.py | 107 +- pyweb/mumble/mctl.py | 77 +- pyweb/mumble/mmobjects.py | 623 +++---- pyweb/mumble/models.py | 1461 +++++++++-------- pyweb/mumble/murmurenvutils.py | 395 ++--- pyweb/mumble/templatetags/mumble_extras.py | 41 +- pyweb/mumble/testrunner.py | 149 +- pyweb/mumble/tests.py | 365 ++-- pyweb/mumble/urls.py | 31 +- pyweb/mumble/utils.py | 39 +- pyweb/mumble/views.py | 708 ++++---- pyweb/processors.py | 9 +- pyweb/settings.py | 103 +- pyweb/urls.py | 37 +- pyweb/views.py | 39 +- 33 files changed, 4178 insertions(+), 4174 deletions(-) diff --git a/muco.py b/muco.py index d495e62..f46bad9 100755 --- a/muco.py +++ b/muco.py @@ -1,5 +1,6 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright (C) 2009, Michael "Svedrin" Ziegler @@ -16,7 +17,7 @@ """ # Set this to the same path you used in settings.py, or None for auto-detection. -MUMBLE_DJANGO_ROOT = None; +MUMBLE_DJANGO_ROOT = None ### DO NOT CHANGE ANYTHING BELOW THIS LINE ### @@ -25,7 +26,7 @@ from os.path import join, dirname, abspath, exists # Path auto-detection if not MUMBLE_DJANGO_ROOT or not exists( MUMBLE_DJANGO_ROOT ): - MUMBLE_DJANGO_ROOT = dirname(abspath(__file__)); + MUMBLE_DJANGO_ROOT = dirname(abspath(__file__)) # environment variables sys.path.append( MUMBLE_DJANGO_ROOT ) @@ -39,329 +40,313 @@ os.environ['DJANGO_SETTINGS_MODULE'] = 'pyweb.settings' # Uncomment this line to point the egg cache to /tmp. #os.environ['PYTHON_EGG_CACHE'] = '/tmp/pyeggs' -import locale -import curses -from curses.textpad import Textbox +import locale +import curses +from curses.textpad import Textbox -from django.core.exceptions import ValidationError -from django.db.models.fields.related import ForeignKey -from django.db import models +from django.core.exceptions import ValidationError +from django.db.models.fields.related import ForeignKey +from django.db import models -from mumble.models import * -from mumble.forms import * +from mumble.models import * +from mumble.forms import * locale.setlocale(locale.LC_ALL, '') class BaseWindow( object ): - tabName = "tehBasez"; - - def __init__( self, parentwin, mumble, pos_x, pos_y ): - self.pos = ( pos_x, pos_y ); - self.win = parentwin.subwin( pos_y, pos_x ); - self.mm = mumble; - - self.win.keypad(1); - - def draw( self ): - self.win.addstr( 1, 1, self.tabName ); - - def border( self ): - self.win.border(); - - def enter( self ): - while( True ): - key = self.win.getch(); - if key == curses.KEY_UP: - return; + tabName = "tehBasez" + + def __init__( self, parentwin, mumble, pos_x, pos_y ): + self.pos = ( pos_x, pos_y ) + self.win = parentwin.subwin( pos_y, pos_x ) + self.mm = mumble + + self.win.keypad(1) + + def draw( self ): + self.win.addstr( 1, 1, self.tabName ) + + def border( self ): + self.win.border() + + def enter( self ): + while( True ): + key = self.win.getch() + if key == curses.KEY_UP: + return class WndChannels( BaseWindow ): - tabName = 'Channels'; - - def printItem( self, item, level ): - namestr = ""; - if item.is_server or item.is_channel: - namestr = "%s (Channel)" % item.name; - else: - namestr = "%s (Player)" % item.name - - self.win.addstr( self.curr_y, 4*level+1, namestr.encode(locale.getpreferredencoding()) ) - self.curr_y += 1; - - def draw( self ): - self.curr_y = 1; - self.mm.rootchan.visit( self.printItem ); + tabName = 'Channels' + + def printItem( self, item, level ): + namestr = "" + if item.is_server or item.is_channel: + namestr = "%s (Channel)" % item.name + else: + namestr = "%s (Player)" % item.name + + self.win.addstr( self.curr_y, 4*level+1, namestr.encode(locale.getpreferredencoding()) ) + self.curr_y += 1 + + def draw( self ): + self.curr_y = 1 + self.mm.rootchan.visit( self.printItem ) class WndSettings( BaseWindow ): - tabName = 'Server settings'; - - blacklist = ( 'id', 'sslkey', 'sslcrt' ); - - def __init__( self, parentwin, mumble, pos_x, pos_y ): - BaseWindow.__init__( self, parentwin, mumble, pos_x, pos_y ); - self.form = MumbleAdminForm( instance=mumble ); - - self.editors = {}; - self.fields = [ mf for mf in mumble._meta.fields if mf.name not in self.blacklist ]; - - def getFieldHeight( self, field ): - if isinstance( field, models.TextField ): - return 10; - return 1; - - def getFieldYPos( self, field ): - ypos = 3; - for curr_field in self.fields: - if curr_field is field: - return ypos; - ypos += self.getFieldHeight( curr_field ); - raise ReferenceError( "Field not found!" ); - - def draw( self ): - curr_y = 3; - - for field in self.fields: - value = unicode( getattr( self.mm, field.name ) ); - - self.win.addstr( curr_y, 1, field.verbose_name.encode(locale.getpreferredencoding()) ); - - height = self.getFieldHeight( field ); - winsize = self.win.getmaxyx(); - - editwin = self.win.subwin( height, winsize[1]-31, self.pos[1] + curr_y, self.pos[0] + 30 ); - editwin.keypad(1); - editwin.addstr( value.encode(locale.getpreferredencoding()) ); - editbox = Textbox( editwin ); - - self.editors[field.name] = ( editwin, editbox ); - - curr_y += height; - - def enter( self ): - self.selIdx = 0; - self.selMax = len( self.fields ) - 1; - - while( True ): - # Highlight selected field label - field = self.fields[self.selIdx]; - - self.win.addstr( - self.getFieldYPos(field), 1, - field.verbose_name.encode(locale.getpreferredencoding()), - curses.A_STANDOUT - ); - self.win.refresh(); - - key = self.win.getch(); - - if key == curses.KEY_UP: - if self.selIdx > 0: - self.selIdx -= 1; - else: - return; - - elif key == curses.KEY_DOWN and self.selIdx < self.selMax: - self.selIdx += 1; - - elif key in ( ord('q'), ord('Q') ): - return; - - elif key in ( ord('s'), ord('S') ): - try: - self.mm.save(); - except Exception, instance: - msg = unicode( instance ); - else: - msg = "Your settings have been saved."; - self.win.addstr( 1, 5, msg.encode(locale.getpreferredencoding()) ); - - elif key in ( curses.KEY_RIGHT, curses.KEY_ENTER, ord('\n') ): - valid = False; - while not valid: - self.editors[field.name][1].edit(); - try: - setattr( self.mm, field.name, - field.to_python( self.editors[field.name][1].gather().strip() ) - ); - except ValidationError, instance: - msg = unicode( instance ); - self.win.addstr( 1, 5, msg.encode(locale.getpreferredencoding()), curses.A_STANDOUT ); - self.win.refresh(); - else: - valid = True; - self.win.move( 1, 5 ); - self.win.clrtoeol(); - - self.editors[field.name][0].refresh(); - - self.win.addstr( - self.getFieldYPos(field), 1, - field.verbose_name.encode(locale.getpreferredencoding()) - ); + tabName = 'Server settings' + + blacklist = ( 'id', 'sslkey', 'sslcrt' ) + + def __init__( self, parentwin, mumble, pos_x, pos_y ): + BaseWindow.__init__( self, parentwin, mumble, pos_x, pos_y ) + self.form = MumbleAdminForm( instance=mumble ) + + self.editors = {} + self.fields = [ mf for mf in mumble._meta.fields if mf.name not in self.blacklist ] + + def getFieldHeight( self, field ): + if isinstance( field, models.TextField ): + return 10 + return 1 + + def getFieldYPos( self, field ): + ypos = 3 + for curr_field in self.fields: + if curr_field is field: + return ypos + ypos += self.getFieldHeight( curr_field ) + raise ReferenceError( "Field not found!" ) + + def draw( self ): + curr_y = 3 + + for field in self.fields: + value = unicode( getattr( self.mm, field.name ) ) + + self.win.addstr( curr_y, 1, field.verbose_name.encode(locale.getpreferredencoding()) ) + + height = self.getFieldHeight( field ) + winsize = self.win.getmaxyx() + + editwin = self.win.subwin( height, winsize[1]-31, self.pos[1] + curr_y, self.pos[0] + 30 ) + editwin.keypad(1) + editwin.addstr( value.encode(locale.getpreferredencoding()) ) + editbox = Textbox( editwin ) + + self.editors[field.name] = ( editwin, editbox ) + + curr_y += height + + def enter( self ): + self.selIdx = 0 + self.selMax = len( self.fields ) - 1 + + while( True ): + # Highlight selected field label + field = self.fields[self.selIdx] + + self.win.addstr( + self.getFieldYPos(field), 1, + field.verbose_name.encode(locale.getpreferredencoding()), + curses.A_STANDOUT + ) + self.win.refresh() + + key = self.win.getch() + + if key == curses.KEY_UP: + if self.selIdx > 0: + self.selIdx -= 1 + else: + return + + elif key == curses.KEY_DOWN and self.selIdx < self.selMax: + self.selIdx += 1 + + elif key in ( ord('q'), ord('Q') ): + return + + elif key in ( ord('s'), ord('S') ): + try: + self.mm.save() + except Exception, instance: + msg = unicode( instance ) + else: + msg = "Your settings have been saved." + self.win.addstr( 1, 5, msg.encode(locale.getpreferredencoding()) ) + + elif key in ( curses.KEY_RIGHT, curses.KEY_ENTER, ord('\n') ): + valid = False + while not valid: + self.editors[field.name][1].edit() + try: + setattr( self.mm, field.name, + field.to_python( self.editors[field.name][1].gather().strip() ) + ) + except ValidationError, instance: + msg = unicode( instance ) + self.win.addstr( 1, 5, msg.encode(locale.getpreferredencoding()), curses.A_STANDOUT ) + self.win.refresh() + else: + valid = True + self.win.move( 1, 5 ) + self.win.clrtoeol() + + self.editors[field.name][0].refresh() + + self.win.addstr( + self.getFieldYPos(field), 1, + field.verbose_name.encode(locale.getpreferredencoding()) + ) class WndUsers( BaseWindow ): - tabName = 'Registered users'; - - def draw( self ): - curr_y = 3; - - for acc in self.mm.mumbleuser_set.all(): - self.win.addstr( curr_y, 1, acc.name.encode(locale.getpreferredencoding()) ); - curr_y += 1; - + tabName = 'Registered users' + + def draw( self ): + curr_y = 3 + + for acc in self.mm.mumbleuser_set.all(): + self.win.addstr( curr_y, 1, acc.name.encode(locale.getpreferredencoding()) ) + curr_y += 1 + class MumbleForm( object ): - def __init__( self, parentwin, mumble, pos_x, pos_y ): - self.pos = ( pos_x, pos_y ); - self.win = parentwin.subwin( pos_y, pos_x ); - self.mm = mumble; - - self.win.keypad(1); - - self.windows = ( - WndChannels( self.win, mumble, pos_x + 2, pos_y + 2 ), - WndSettings( self.win, mumble, pos_x + 2, pos_y + 2 ), - WndUsers( self.win, mumble, pos_x + 2, pos_y + 2 ), - ); - - self.curridx = 0; - self.currmax = len( self.windows ) - 1; - - currwin = property( lambda self: self.windows[self.curridx], None ); - - def mvwin( self, pos_x, pos_y ): - self.win.mvwin( pos_y, pos_x ); - - def mvdefault( self ): - self.win.mvwin( self.pos[1], self.pos[0] ); - - def draw( self ): - self.win.addstr( 0, 0, self.mm.name.encode(locale.getpreferredencoding()) ); - - def drawTabs( self ): - first = True; - for subwin in self.windows: - flags = 0; - if subwin is self.currwin: flags |= curses.A_STANDOUT; - - if first: - self.win.addstr( 1, 2, "%-20s" % subwin.tabName, flags ); - first = False; - else: - self.win.addstr( "%-20s" % subwin.tabName, flags ); - - def enter( self ): - self.drawTabs(); - self.currwin.draw(); - self.currwin.border(); - - while( True ): - key = self.win.getch(); - - if key == curses.KEY_LEFT and self.curridx > 0: - self.curridx -= 1; - - elif key == curses.KEY_RIGHT and self.curridx < self.currmax: - self.curridx += 1; - - elif key in ( ord('q'), ord('Q'), curses.KEY_UP ): - return; - - elif key in ( curses.KEY_ENTER, curses.KEY_DOWN, ord('\n') ): - self.currwin.enter(); - - self.win.clear(); - self.draw(); - self.drawTabs(); - self.currwin.draw(); - self.currwin.border(); - self.win.refresh(); - + def __init__( self, parentwin, mumble, pos_x, pos_y ): + self.pos = ( pos_x, pos_y ) + self.win = parentwin.subwin( pos_y, pos_x ) + self.mm = mumble + + self.win.keypad(1) + + self.windows = ( + WndChannels( self.win, mumble, pos_x + 2, pos_y + 2 ), + WndSettings( self.win, mumble, pos_x + 2, pos_y + 2 ), + WndUsers( self.win, mumble, pos_x + 2, pos_y + 2 ), + ) + + self.curridx = 0 + self.currmax = len( self.windows ) - 1 + + currwin = property( lambda self: self.windows[self.curridx], None ) + + def mvwin( self, pos_x, pos_y ): + self.win.mvwin( pos_y, pos_x ) + + def mvdefault( self ): + self.win.mvwin( self.pos[1], self.pos[0] ) + + def draw( self ): + self.win.addstr( 0, 0, self.mm.name.encode(locale.getpreferredencoding()) ) + + def drawTabs( self ): + first = True + for subwin in self.windows: + flags = 0 + if subwin is self.currwin: flags |= curses.A_STANDOUT + + if first: + self.win.addstr( 1, 2, "%-20s" % subwin.tabName, flags ) + first = False + else: + self.win.addstr( "%-20s" % subwin.tabName, flags ) + + def enter( self ): + self.drawTabs() + self.currwin.draw() + self.currwin.border() + + while( True ): + key = self.win.getch() + + if key == curses.KEY_LEFT and self.curridx > 0: + self.curridx -= 1 + + elif key == curses.KEY_RIGHT and self.curridx < self.currmax: + self.curridx += 1 + + elif key in ( ord('q'), ord('Q'), curses.KEY_UP ): + return + + elif key in ( curses.KEY_ENTER, curses.KEY_DOWN, ord('\n') ): + self.currwin.enter() + + self.win.clear() + self.draw() + self.drawTabs() + self.currwin.draw() + self.currwin.border() + self.win.refresh() + def main( stdscr ): - first_y = 3; - curr_y = first_y; - - mumbles = list(); - - for mm in Mumble.objects.all().order_by( "name", "id" ): - mwin = MumbleForm( stdscr, mm, pos_x=5, pos_y=curr_y ); - mumbles.append( mwin ); - mwin.draw(); - curr_y += 1; - - selectedIdx = 0; - selectedMax = len(mumbles) - 1; - - myname = "Mumble Commander"; - - while( True ): - selectedObj = mumbles[selectedIdx]; - - maxyx = stdscr.getmaxyx(); - stdscr.addstr( 0, maxyx[1] / 2 - len(myname)/2, myname, curses.A_UNDERLINE ); - - # Draw selection marker - stdscr.addstr( first_y + selectedIdx, 3, '*' ); - stdscr.refresh(); - - key = stdscr.getch(); - if key == curses.KEY_UP: - stdscr.addstr( first_y + selectedIdx, 3, ' ' ); - selectedIdx -= 1; - - elif key == curses.KEY_DOWN: - stdscr.addstr( first_y + selectedIdx, 3, ' ' ); - selectedIdx += 1; - - elif key in ( curses.KEY_RIGHT, curses.KEY_ENTER, ord('\n') ): - stdscr.clear(); - stdscr.addstr( 0, maxyx[1] / 2 - len(myname)/2, myname, curses.A_UNDERLINE ); - selectedObj.mvwin( 5, first_y ); - selectedObj.draw(); - stdscr.refresh(); - - selectedObj.enter(); - - stdscr.clear(); - selectedObj.mvdefault(); - for mwin in mumbles: - mwin.draw(); - - elif key in ( ord('q'), ord('Q') ): - return; - - if selectedIdx < 0: selectedIdx = 0; - elif selectedIdx > selectedMax: selectedIdx = selectedMax; - + first_y = 3 + curr_y = first_y + mumbles = list() + for mm in Mumble.objects.all().order_by( "name", "id" ): + mwin = MumbleForm( stdscr, mm, pos_x=5, pos_y=curr_y ) + mumbles.append( mwin ) + mwin.draw() + curr_y += 1 + selectedIdx = 0 + selectedMax = len(mumbles) - 1 -if __name__ == '__main__': - #parser = OptionParser(); - #parser.add_option( "-v", "--verbose", help="verbose output messages", default=False, action="store_true" ); - #parser.add_option( "-n", "--num", help="size of the Matrix", default=4, type = 'int' ); - #parser.add_option( "-s", "--sure", help="don't prompt if num >= 10", default=False, action="store_true" ); - #options, args = parser.parse_args(); - - curses.wrapper( main ); + myname = "Mumble Commander" + while( True ): + selectedObj = mumbles[selectedIdx] + maxyx = stdscr.getmaxyx() + stdscr.addstr( 0, maxyx[1] / 2 - len(myname)/2, myname, curses.A_UNDERLINE ) + # Draw selection marker + stdscr.addstr( first_y + selectedIdx, 3, '*' ) + stdscr.refresh() + key = stdscr.getch() + if key == curses.KEY_UP: + stdscr.addstr( first_y + selectedIdx, 3, ' ' ) + selectedIdx -= 1 + elif key == curses.KEY_DOWN: + stdscr.addstr( first_y + selectedIdx, 3, ' ' ) + selectedIdx += 1 + elif key in ( curses.KEY_RIGHT, curses.KEY_ENTER, ord('\n') ): + stdscr.clear() + stdscr.addstr( 0, maxyx[1] / 2 - len(myname)/2, myname, curses.A_UNDERLINE ) + selectedObj.mvwin( 5, first_y ) + selectedObj.draw() + stdscr.refresh() + selectedObj.enter() + stdscr.clear() + selectedObj.mvdefault() + for mwin in mumbles: + mwin.draw() + elif key in ( ord('q'), ord('Q') ): + return + if selectedIdx < 0: selectedIdx = 0 + elif selectedIdx > selectedMax: selectedIdx = selectedMax +if __name__ == '__main__': + #parser = OptionParser() + #parser.add_option( "-v", "--verbose", help="verbose output messages", default=False, action="store_true" ) + #parser.add_option( "-n", "--num", help="size of the Matrix", default=4, type = 'int' ) + #parser.add_option( "-s", "--sure", help="don't prompt if num >= 10", default=False, action="store_true" ) + #options, args = parser.parse_args() + curses.wrapper( main ) diff --git a/mumble-django.wsgi b/mumble-django.wsgi index e10a525..5bd1322 100644 --- a/mumble-django.wsgi +++ b/mumble-django.wsgi @@ -9,7 +9,7 @@ from os.path import join, dirname, abspath, exists # Path auto-detection if not MUMBLE_DJANGO_ROOT or not exists( MUMBLE_DJANGO_ROOT ): - MUMBLE_DJANGO_ROOT = dirname(abspath(__file__)); + MUMBLE_DJANGO_ROOT = dirname(abspath(__file__)) # environment variables sys.path.append( MUMBLE_DJANGO_ROOT ) diff --git a/munin.py b/munin.py index e556a63..c90cef2 100755 --- a/munin.py +++ b/munin.py @@ -1,5 +1,6 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright (C) 2009, Michael "Svedrin" Ziegler @@ -16,7 +17,7 @@ """ # Set this to the same path you used in settings.py, or None for auto-detection. -MUMBLE_DJANGO_ROOT = None; +MUMBLE_DJANGO_ROOT = None ### DO NOT CHANGE ANYTHING BELOW THIS LINE ### @@ -25,7 +26,7 @@ from os.path import join, dirname, abspath, exists # Path auto-detection if not MUMBLE_DJANGO_ROOT or not exists( MUMBLE_DJANGO_ROOT ): - MUMBLE_DJANGO_ROOT = dirname(abspath(__file__)); + MUMBLE_DJANGO_ROOT = dirname(abspath(__file__)) # environment variables sys.path.append( MUMBLE_DJANGO_ROOT ) @@ -50,46 +51,46 @@ categ = getattr( settings, "MUNIN_CATEGORY", "network" ) def get_running_instances(): - for server in MumbleServer.objects.all(): - if not server.online: - continue - runinst = server.ctl.getBootedServers() - for inst in server.mumble_set.order_by("srvid").filter( srvid__in=runinst ): - yield inst + for server in MumbleServer.objects.all(): + if not server.online: + continue + runinst = server.ctl.getBootedServers() + for inst in server.mumble_set.order_by("srvid").filter( srvid__in=runinst ): + yield inst if sys.argv[-1] == 'config': - prefenc = locale.getpreferredencoding() - - print "graph_vlabel Users" - print "graph_args --base 1000" - print "graph_title", title - print "graph_category", categ - - for mumble in get_running_instances(): - print "srv%d.label %s" % ( mumble.id, mumble.name.replace('#', '').encode(prefenc, "replace") ); - if mumble.connecturl: - print "srv%d.info %s" % ( mumble.id, mumble.connecturl ); - if mumble.users: - print "srv%d.warning %d" % ( mumble.id, int( mumble.users * warn ) ); - print "srv%d.critical %d" % ( mumble.id, int( mumble.users * crit ) ); + prefenc = locale.getpreferredencoding() + + print "graph_vlabel Users" + print "graph_args --base 1000" + print "graph_title", title + print "graph_category", categ + + for mumble in get_running_instances(): + print "srv%d.label %s" % ( mumble.id, mumble.name.replace('#', '').encode(prefenc, "replace") ) + if mumble.connecturl: + print "srv%d.info %s" % ( mumble.id, mumble.connecturl ) + if mumble.users: + print "srv%d.warning %d" % ( mumble.id, int( mumble.users * warn ) ) + print "srv%d.critical %d" % ( mumble.id, int( mumble.users * crit ) ) elif sys.argv[-1] == 'autoconf': - if Mumble.objects.count() == 0: - print "no (no servers configured)"; - else: - # check if connecting works - try: - for mumble in get_running_instances(): - mumble.ctl - except Exception, instance: - print "no (can't connect to server %s: %s)" % ( mumble.name, instance ); - else: - print "yes"; + if Mumble.objects.count() == 0: + print "no (no servers configured)" + else: + # check if connecting works + try: + for mumble in get_running_instances(): + mumble.ctl + except Exception, instance: + print "no (can't connect to server %s: %s)" % ( mumble.name, instance ) + else: + print "yes" else: - for mumble in get_running_instances(): - print "srv%d.value %d" % ( mumble.id, len( mumble.ctl.getPlayers( mumble.srvid ) ) ); + for mumble in get_running_instances(): + print "srv%d.value %d" % ( mumble.id, len( mumble.ctl.getPlayers( mumble.srvid ) ) ) diff --git a/pyweb/manage.py b/pyweb/manage.py index 7b1babb..a7da31a 100755 --- a/pyweb/manage.py +++ b/pyweb/manage.py @@ -1,4 +1,4 @@ -#!/usr/bin/python +#!/usr/bin/env python # -*- coding: utf-8 -*- """ diff --git a/pyweb/mucli.py b/pyweb/mucli.py index 54c7f47..d3fefa0 100755 --- a/pyweb/mucli.py +++ b/pyweb/mucli.py @@ -1,5 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright (C) 2010, Michael "Svedrin" Ziegler @@ -22,8 +23,8 @@ import os, sys import inspect import getpass -from optparse import OptionParser -from mumble.mctl import MumbleCtlBase +from optparse import OptionParser +from mumble.mctl import MumbleCtlBase usage = """Usage: %prog [options] [] [] @@ -39,153 +40,153 @@ as False. parser = OptionParser(usage=usage) parser.add_option( "-d", "--django-settings", - help="if specified, get connstring and slice defaults from the given Django " - "settings module. Default: empty.", - default=None - ) + help="if specified, get connstring and slice defaults from the given Django " + "settings module. Default: empty.", + default=None + ) parser.add_option( "-c", "--connstring", - help="connection string to use. Default is '%s'." % DEFAULT_CONNSTRING, - default=None - ) + help="connection string to use. Default is '%s'." % DEFAULT_CONNSTRING, + default=None + ) parser.add_option( "-i", "--icesecret", - help="Ice secret to use in the connection. Also see --asksecret.", - default=None - ) + help="Ice secret to use in the connection. Also see --asksecret.", + default=None + ) parser.add_option( "-a", "--asksecret", - help="Ask for the Ice secret on the shell instead of taking it from the command line.", - action="store_true", default=False - ) + help="Ask for the Ice secret on the shell instead of taking it from the command line.", + action="store_true", default=False + ) parser.add_option( "-s", "--slice", - help="path to the slice file. Default is '%s'." % DEFAULT_SLICEFILE, - default=None - ) + help="path to the slice file. Default is '%s'." % DEFAULT_SLICEFILE, + default=None + ) parser.add_option( "-e", "--encoding", - help="Character set arguments are encoded in. Default: Read from LANG env variable with fallback to UTF-8.", - default=None - ) + help="Character set arguments are encoded in. Default: Read from LANG env variable with fallback to UTF-8.", + default=None + ) parser.add_option( - "-v", "--verbose", - help="Show verbose messages on stderr", - default=False, - action="store_true" - ) + "-v", "--verbose", + help="Show verbose messages on stderr", + default=False, + action="store_true" + ) options, progargs = parser.parse_args() if options.django_settings is not None: - if options.verbose: - print >> sys.stderr, "Reading settings from module '%s'." % options.django_settings - - os.environ['DJANGO_SETTINGS_MODULE'] = options.django_settings - from django.conf import settings - - if options.connstring is None: - if options.verbose: - print >> sys.stderr, "Setting connstring from settings module" - options.connstring = settings.DEFAULT_CONN - - if options.slice is None: - if options.verbose: - print >> sys.stderr, "Setting slice from settings module" - options.slice = settings.SLICE + if options.verbose: + print >> sys.stderr, "Reading settings from module '%s'." % options.django_settings + + os.environ['DJANGO_SETTINGS_MODULE'] = options.django_settings + from django.conf import settings + + if options.connstring is None: + if options.verbose: + print >> sys.stderr, "Setting connstring from settings module" + options.connstring = settings.DEFAULT_CONN + + if options.slice is None: + if options.verbose: + print >> sys.stderr, "Setting slice from settings module" + options.slice = settings.SLICE else: - if options.connstring is None: - if options.verbose: - print >> sys.stderr, "Setting default connstring" - options.connstring = DEFAULT_CONNSTRING - - if options.slice is None: - if options.verbose: - print >> sys.stderr, "Setting default slice" - options.slice = DEFAULT_SLICEFILE + if options.connstring is None: + if options.verbose: + print >> sys.stderr, "Setting default connstring" + options.connstring = DEFAULT_CONNSTRING + + if options.slice is None: + if options.verbose: + print >> sys.stderr, "Setting default slice" + options.slice = DEFAULT_SLICEFILE if options.encoding is None: - try: - locale = os.environ['LANG'] - _, options.encoding = locale.split('.') - except (KeyError, ValueError): - options.encoding = "UTF-8" + try: + locale = os.environ['LANG'] + _, options.encoding = locale.split('.') + except (KeyError, ValueError): + options.encoding = "UTF-8" if options.verbose: - print >> sys.stderr, "Connection info:" - print >> sys.stderr, " Connstring: %s" % options.connstring - print >> sys.stderr, " Slice: %s" % options.slice - print >> sys.stderr, "Encoding: %s" % options.encoding + print >> sys.stderr, "Connection info:" + print >> sys.stderr, " Connstring: %s" % options.connstring + print >> sys.stderr, " Slice: %s" % options.slice + print >> sys.stderr, "Encoding: %s" % options.encoding if options.asksecret or options.icesecret == '': - options.icesecret = getpass.getpass( "Ice secret: " ) + options.icesecret = getpass.getpass( "Ice secret: " ) ctl = MumbleCtlBase.newInstance( options.connstring, options.slice, options.icesecret ) if not progargs: - # Print available methods. - for method in inspect.getmembers( ctl ): - if method[0][0] == '_' or not callable( method[1] ): - continue - - if hasattr( method[1], "innerfunc" ): - args = inspect.getargspec( method[1].innerfunc )[0] - else: - args = inspect.getargspec( method[1] )[0] - - if len( args ) > 1: - if args[0] == 'self': - print "%s( %s )" % ( method[0], ', '.join( args[1:] ) ) - else: - print "%s()" % method[0] + # Print available methods. + for method in inspect.getmembers( ctl ): + if method[0][0] == '_' or not callable( method[1] ): + continue + + if hasattr( method[1], "innerfunc" ): + args = inspect.getargspec( method[1].innerfunc )[0] + else: + args = inspect.getargspec( method[1] )[0] + + if len( args ) > 1: + if args[0] == 'self': + print "%s( %s )" % ( method[0], ', '.join( args[1:] ) ) + else: + print "%s()" % method[0] else: - # function name given. check if its args matches ours, if yes call it, if not print usage - if options.verbose: - print >> sys.stderr, "Method name: %s" % progargs[0] - - method = getattr( ctl, progargs[0] ) - if hasattr( method, "innerfunc" ): - method = method.innerfunc - - args = inspect.getargspec( method )[0] - - if len(progargs) == len(args) and args[0] == 'self': - if len(args) == 1: - print method(ctl) - else: - cleanargs = [] - for param in progargs[1:]: - try: - argtype, argval = param.split(':', 1) - except ValueError: - cleanargs.append( param.decode(options.encoding) ) - else: - cleanval = { - 'bool': lambda val: val in ('True', 'true', '1', 'Yes', 'yes'), - 'int': int, - 'float': float, - 'string': str - }[ argtype ]( argval ) - - if argtype == 'string': - cleanval = cleanval.decode(options.encoding) - cleanargs.append(cleanval) - - if options.verbose: - print >> sys.stderr, "Call arguments: %s" % repr(cleanargs) - - print method( ctl, *cleanargs ) - - elif len(args) == 1: - print >> sys.stderr, "Method '%s' does not take any arguments." % progargs[0] - print >> sys.stderr, "Expected %s()" % progargs[0] - - else: - print >> sys.stderr, "Invalid arguments for method '%s': %s" % ( progargs[0], ', '.join( progargs[1:] ) ) - print >> sys.stderr, "Expected %s( %s )" % ( progargs[0], ', '.join( args[1:] ) ) + # function name given. check if its args matches ours, if yes call it, if not print usage + if options.verbose: + print >> sys.stderr, "Method name: %s" % progargs[0] + + method = getattr( ctl, progargs[0] ) + if hasattr( method, "innerfunc" ): + method = method.innerfunc + + args = inspect.getargspec( method )[0] + + if len(progargs) == len(args) and args[0] == 'self': + if len(args) == 1: + print method(ctl) + else: + cleanargs = [] + for param in progargs[1:]: + try: + argtype, argval = param.split(':', 1) + except ValueError: + cleanargs.append( param.decode(options.encoding) ) + else: + cleanval = { + 'bool': lambda val: val in ('True', 'true', '1', 'Yes', 'yes'), + 'int': int, + 'float': float, + 'string': str + }[ argtype ]( argval ) + + if argtype == 'string': + cleanval = cleanval.decode(options.encoding) + cleanargs.append(cleanval) + + if options.verbose: + print >> sys.stderr, "Call arguments: %s" % repr(cleanargs) + + print method( ctl, *cleanargs ) + + elif len(args) == 1: + print >> sys.stderr, "Method '%s' does not take any arguments." % progargs[0] + print >> sys.stderr, "Expected %s()" % progargs[0] + + else: + print >> sys.stderr, "Invalid arguments for method '%s': %s" % ( progargs[0], ', '.join( progargs[1:] ) ) + print >> sys.stderr, "Expected %s( %s )" % ( progargs[0], ', '.join( args[1:] ) ) diff --git a/pyweb/mumble/MumbleCtlDbus.py b/pyweb/mumble/MumbleCtlDbus.py index baa3679..84944d2 100644 --- a/pyweb/mumble/MumbleCtlDbus.py +++ b/pyweb/mumble/MumbleCtlDbus.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009, withgod @@ -15,316 +16,316 @@ * GNU General Public License for more details. """ -from PIL import Image -from struct import pack, unpack -from zlib import compress, decompress +from PIL import Image +from struct import pack, unpack +from zlib import compress, decompress -from mctl import MumbleCtlBase -from utils import ObjectInfo +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 ); + """ 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): - 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: - print channel; - ret[ channel[0] ] = ObjectInfo( - id = int(channel[0]), - name = unicode(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 = unicode( 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 = unicode(rule[4]), - allow = int(rule[5]), - deny = int(rule[6]), - ) - for rule in raw_acls - ]; - - groups = [ ObjectInfo( - name = unicode(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 = 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 + 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): + 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: + print channel + ret[ channel[0] ] = ObjectInfo( + id = int(channel[0]), + name = unicode(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 = unicode( 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 = unicode(rule[4]), + allow = int(rule[5]), + deny = int(rule[6]), + ) + for rule in raw_acls + ] + + groups = [ ObjectInfo( + name = unicode(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 = 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 ) ) - ) + 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 ) ) + ) diff --git a/pyweb/mumble/MumbleCtlIce.py b/pyweb/mumble/MumbleCtlIce.py index 2a945d8..069a9ed 100644 --- a/pyweb/mumble/MumbleCtlIce.py +++ b/pyweb/mumble/MumbleCtlIce.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009, withgod @@ -15,591 +16,591 @@ * GNU General Public License for more details. """ -from time import time -from functools import wraps -from StringIO import StringIO -from os.path import exists, join -from os import unlink, name as os_name -from PIL import Image -from struct import pack, unpack -from zlib import compress, decompress, error +from time import time +from functools import wraps +from StringIO import StringIO +from os.path import exists, join +from os import unlink, name as os_name +from PIL import Image +from struct import pack, unpack +from zlib import compress, decompress, error -from mctl import MumbleCtlBase +from mctl import MumbleCtlBase -from utils import ObjectInfo +from utils import ObjectInfo import Ice, IcePy, tempfile def loadSlice( slicefile ): - """ Load the slice file with the correct include dir set, if possible. """ - icepath = Ice.getSliceDir() - if not icepath: - # last resort, let's hope to christ this works (won't for >=1.2.3) - Ice.loadSlice( slicefile ) - else: - Ice.loadSlice( '', ['-I' + icepath, slicefile ] ) + """ Load the slice file with the correct include dir set, if possible. """ + icepath = Ice.getSliceDir() + if not icepath: + # last resort, let's hope to christ this works (won't for >=1.2.3) + Ice.loadSlice( slicefile ) + else: + Ice.loadSlice( '', ['-I' + icepath, slicefile ] ) 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. - """ - - @wraps(func) - def protection_wrapper( self, *args, **kwargs ): - """ Call the original function and catch Ice exceptions. """ - try: - return func( self, *args, **kwargs ); - except Ice.Exception, err: - raise err; - protection_wrapper.innerfunc = func - - return protection_wrapper; + """ 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. + """ + + @wraps(func) + def protection_wrapper( self, *args, **kwargs ): + """ Call the original function and catch Ice exceptions. """ + try: + return func( self, *args, **kwargs ) + except Ice.Exception, err: + raise err + protection_wrapper.innerfunc = func + + return protection_wrapper @protectDjangoErrPage def MumbleCtlIce( connstring, slicefile=None, icesecret=None ): - """ Choose the correct Ice handler to use (1.1.8 or 1.2.x), and make sure the - Murmur version matches the slice Version. - - Optional parameters are the path to the slice file and the Ice secret - necessary to authenticate to Murmur. - - The path can be omitted only if running Murmur 1.2.3 or later, which - exports a getSlice method to retrieve the Slice from. - """ - - prop = Ice.createProperties([]) - prop.setProperty("Ice.ImplicitContext", "Shared") - - idd = Ice.InitializationData() - idd.properties = prop - - ice = Ice.initialize(idd) - - if icesecret: - ice.getImplicitContext().put( "secret", icesecret.encode("utf-8") ) - - prx = ice.stringToProxy( connstring.encode("utf-8") ) - - try: - prx.ice_ping() - except Ice.Exception: - raise EnvironmentError( "Murmur does not appear to be listening on this address (Ice ping failed)." ) - - try: - import Murmur - except ImportError: - # Try loading the Slice from Murmur directly via its getSlice method. - # See scripts/testdynamic.py in Mumble's Git repository. - try: - slice = IcePy.Operation( 'getSlice', - Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, - True, (), (), (), IcePy._t_string, () - ).invoke(prx, ((), None)) - except (TypeError, Ice.OperationNotExistException): - 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'." ) - - try: - loadSlice( slicefile ) - except RuntimeError: - raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." ) - else: - if os_name == "nt": - # It weren't Windows if it didn't need to be treated differently. *sigh* - temppath = join( tempfile.gettempdir(), "Murmur.ice" ) - slicetemp = open( temppath, "w+b" ) - try: - slicetemp.write( slice ) - finally: - slicetemp.close() - try: - loadSlice( temppath ) - except RuntimeError: - raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." ) - finally: - unlink(temppath) - else: - slicetemp = tempfile.NamedTemporaryFile( suffix='.ice' ) - try: - slicetemp.write( slice ) - slicetemp.flush() - loadSlice( slicetemp.name ) - except RuntimeError: - raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." ) - finally: - slicetemp.close() - - import Murmur - - meta = Murmur.MetaPrx.checkedCast(prx) - - murmurversion = meta.getVersion()[:3] - - if murmurversion == (1, 1, 8): - return MumbleCtlIce_118( connstring, meta ); - - elif murmurversion[:2] == (1, 2): - if murmurversion[2] < 2: - return MumbleCtlIce_120( connstring, meta ); - - elif murmurversion[2] == 2: - return MumbleCtlIce_122( connstring, meta ); - - elif murmurversion[2] == 3: - return MumbleCtlIce_123( connstring, meta ); - - raise NotImplementedError( "No ctl object available for Murmur version %d.%d.%d" % tuple(murmurversion) ) + """ Choose the correct Ice handler to use (1.1.8 or 1.2.x), and make sure the + Murmur version matches the slice Version. + + Optional parameters are the path to the slice file and the Ice secret + necessary to authenticate to Murmur. + + The path can be omitted only if running Murmur 1.2.3 or later, which + exports a getSlice method to retrieve the Slice from. + """ + + prop = Ice.createProperties([]) + prop.setProperty("Ice.ImplicitContext", "Shared") + + idd = Ice.InitializationData() + idd.properties = prop + + ice = Ice.initialize(idd) + + if icesecret: + ice.getImplicitContext().put( "secret", icesecret.encode("utf-8") ) + + prx = ice.stringToProxy( connstring.encode("utf-8") ) + + try: + prx.ice_ping() + except Ice.Exception: + raise EnvironmentError( "Murmur does not appear to be listening on this address (Ice ping failed)." ) + + try: + import Murmur + except ImportError: + # Try loading the Slice from Murmur directly via its getSlice method. + # See scripts/testdynamic.py in Mumble's Git repository. + try: + slice = IcePy.Operation( 'getSlice', + Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, + True, (), (), (), IcePy._t_string, () + ).invoke(prx, ((), None)) + except (TypeError, Ice.OperationNotExistException): + 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'." ) + + try: + loadSlice( slicefile ) + except RuntimeError: + raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." ) + else: + if os_name == "nt": + # It weren't Windows if it didn't need to be treated differently. *sigh* + temppath = join( tempfile.gettempdir(), "Murmur.ice" ) + slicetemp = open( temppath, "w+b" ) + try: + slicetemp.write( slice ) + finally: + slicetemp.close() + try: + loadSlice( temppath ) + except RuntimeError: + raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." ) + finally: + unlink(temppath) + else: + slicetemp = tempfile.NamedTemporaryFile( suffix='.ice' ) + try: + slicetemp.write( slice ) + slicetemp.flush() + loadSlice( slicetemp.name ) + except RuntimeError: + raise RuntimeError( "Slice preprocessing failed. Please check your server's error log." ) + finally: + slicetemp.close() + + import Murmur + + meta = Murmur.MetaPrx.checkedCast(prx) + + murmurversion = meta.getVersion()[:3] + + if murmurversion == (1, 1, 8): + return MumbleCtlIce_118( connstring, meta ) + + elif murmurversion[:2] == (1, 2): + if murmurversion[2] < 2: + return MumbleCtlIce_120( connstring, meta ) + + elif murmurversion[2] == 2: + return MumbleCtlIce_122( connstring, meta ) + + elif murmurversion[2] == 3: + return MumbleCtlIce_123( connstring, meta ) + + raise NotImplementedError( "No ctl object available for Murmur version %d.%d.%d" % tuple(murmurversion) ) 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. - try: - decompressed = decompress( texture ); - except error, err: - raise ValueError( err ) - # 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 = 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 + 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. + try: + decompressed = decompress( texture ) + except error, err: + raise ValueError( err ) + # 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 = 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): - userdata = self._getIceServerObject(srvid).getUsers(); - for key in userdata: - if isinstance( userdata[key], str ): - userdata[key] = userdata[key].decode( "UTF-8" ) - return userdata - - @protectDjangoErrPage - def getState(self, srvid, sessionid): - userdata = self._getIceServerObject(srvid).getState(sessionid); - for key in userdata.__dict__: - attr = getattr( userdata, key ) - if isinstance( attr, str ): - setattr( userdata, key, attr.decode( "UTF-8" ) ) - return userdata - - @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 ); - - @protectDjangoErrPage - def getBans(self, srvid): - return self._getIceServerObject(srvid).getBans(); - - @protectDjangoErrPage - def setBans(self, srvid, bans): - return self._getIceServerObject(srvid).setBans(bans); - - @protectDjangoErrPage - def addBanForSession(self, srvid, sessionid, **kwargs): - session = self.getState(srvid, sessionid); - if "bits" not in kwargs: - kwargs["bits"] = 128; - if "start" not in kwargs: - kwargs["start"] = int(time()); - if "duration" not in kwargs: - kwargs["duration"] = 3600; - return self.addBan(srvid, address=session.address, **kwargs); - - @protectDjangoErrPage - def addBan(self, srvid, **kwargs): - for key in kwargs: - if isinstance( kwargs[key], unicode ): - kwargs[key] = kwargs[key].encode("UTF-8") - - from Murmur import Ban - srvbans = self.getBans(srvid); - srvbans.append( Ban( **kwargs ) ); - return self.setBans(srvid, srvbans); - - @protectDjangoErrPage - def kickUser(self, srvid, userid, reason=""): - return self._getIceServerObject(srvid).kickUser( userid, reason.encode("UTF-8") ); + @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): + userdata = self._getIceServerObject(srvid).getUsers() + for key in userdata: + if isinstance( userdata[key], str ): + userdata[key] = userdata[key].decode( "UTF-8" ) + return userdata + + @protectDjangoErrPage + def getState(self, srvid, sessionid): + userdata = self._getIceServerObject(srvid).getState(sessionid) + for key in userdata.__dict__: + attr = getattr( userdata, key ) + if isinstance( attr, str ): + setattr( userdata, key, attr.decode( "UTF-8" ) ) + return userdata + + @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 ) + + @protectDjangoErrPage + def getBans(self, srvid): + return self._getIceServerObject(srvid).getBans() + + @protectDjangoErrPage + def setBans(self, srvid, bans): + return self._getIceServerObject(srvid).setBans(bans) + + @protectDjangoErrPage + def addBanForSession(self, srvid, sessionid, **kwargs): + session = self.getState(srvid, sessionid) + if "bits" not in kwargs: + kwargs["bits"] = 128 + if "start" not in kwargs: + kwargs["start"] = int(time()) + if "duration" not in kwargs: + kwargs["duration"] = 3600 + return self.addBan(srvid, address=session.address, **kwargs) + + @protectDjangoErrPage + def addBan(self, srvid, **kwargs): + for key in kwargs: + if isinstance( kwargs[key], unicode ): + kwargs[key] = kwargs[key].encode("UTF-8") + + from Murmur import Ban + srvbans = self.getBans(srvid) + srvbans.append( Ban( **kwargs ) ) + return self.setBans(srvid, srvbans) + + @protectDjangoErrPage + def kickUser(self, srvid, userid, reason=""): + return self._getIceServerObject(srvid).kickUser( userid, reason.encode("UTF-8") ) class MumbleCtlIce_122(MumbleCtlIce_120): - @protectDjangoErrPage - def getTexture(self, srvid, mumbleid): - raise ValueError( "This method is buggy in 1.2.2, sorry dude." ); - - @protectDjangoErrPage - def setTexture(self, srvid, mumbleid, infile): - buf = StringIO() - infile.save( buf, "PNG" ) - buf.seek(0) - self._getIceServerObject(srvid).setTexture(mumbleid, buf.read()) + @protectDjangoErrPage + def getTexture(self, srvid, mumbleid): + raise ValueError( "This method is buggy in 1.2.2, sorry dude." ) + + @protectDjangoErrPage + def setTexture(self, srvid, mumbleid, infile): + buf = StringIO() + infile.save( buf, "PNG" ) + buf.seek(0) + self._getIceServerObject(srvid).setTexture(mumbleid, buf.read()) class MumbleCtlIce_123(MumbleCtlIce_120): - - @protectDjangoErrPage - def getRawTexture(self, srvid, mumbleid): - return self._getIceServerObject(srvid).getTexture(mumbleid) - - @protectDjangoErrPage - def getTexture(self, srvid, mumbleid): - texture = self.getRawTexture(srvid, mumbleid) - if len(texture) == 0: - raise ValueError( "No Texture has been set." ); - from StringIO import StringIO - try: - return Image.open( StringIO( texture ) ) - except IOError, err: - raise ValueError( err ) - - @protectDjangoErrPage - def setTexture(self, srvid, mumbleid, infile): - buf = StringIO() - infile.save( buf, "PNG" ) - buf.seek(0) - self._getIceServerObject(srvid).setTexture(mumbleid, buf.read()) + + @protectDjangoErrPage + def getRawTexture(self, srvid, mumbleid): + return self._getIceServerObject(srvid).getTexture(mumbleid) + + @protectDjangoErrPage + def getTexture(self, srvid, mumbleid): + texture = self.getRawTexture(srvid, mumbleid) + if len(texture) == 0: + raise ValueError( "No Texture has been set." ) + from StringIO import StringIO + try: + return Image.open( StringIO( texture ) ) + except IOError, err: + raise ValueError( err ) + + @protectDjangoErrPage + def setTexture(self, srvid, mumbleid, infile): + buf = StringIO() + infile.save( buf, "PNG" ) + buf.seek(0) + self._getIceServerObject(srvid).setTexture(mumbleid, buf.read()) diff --git a/pyweb/mumble/__init__.py b/pyweb/mumble/__init__.py index 0681312..b859eb1 100644 --- a/pyweb/mumble/__init__.py +++ b/pyweb/mumble/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler * @@ -16,29 +17,29 @@ version = { 'major': 2, 'minor': 2, 'beta': None } if version['beta']: - version_str = "v%(major)d.%(minor)dbeta%(beta)d" % version + version_str = "v%(major)d.%(minor)dbeta%(beta)d" % version else: - version_str = "v%(major)d.%(minor)d" % version + version_str = "v%(major)d.%(minor)d" % version def getVersions(): - """ Generator that yields all available upstream versions. """ - url = 'http://bitbucket.org/Svedrin/mumble-django/raw/tip/.hgtags' - from urllib2 import urlopen - webtags = urlopen(url) - try: - while True: - line = webtags.readline().strip() - if not line: - raise StopIteration - _, version = line.split(' ') - yield version - finally: - webtags.close() + """ Generator that yields all available upstream versions. """ + url = 'http://bitbucket.org/Svedrin/mumble-django/raw/tip/.hgtags' + from urllib2 import urlopen + webtags = urlopen(url) + try: + while True: + line = webtags.readline().strip() + if not line: + raise StopIteration + _, version = line.split(' ') + yield version + finally: + webtags.close() def getLatestUpstreamVersion(): - """ Return the latest version available upstream. """ - return max(getVersions()) + """ Return the latest version available upstream. """ + return max(getVersions()) def isUptodate(): - """ Check if this version of Mumble-Django is the latest available. """ - return version_str >= getLatestUpstreamVersion() + """ Check if this version of Mumble-Django is the latest available. """ + return version_str >= getLatestUpstreamVersion() diff --git a/pyweb/mumble/admin.py b/pyweb/mumble/admin.py index f66bd9e..0256b3f 100644 --- a/pyweb/mumble/admin.py +++ b/pyweb/mumble/admin.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -22,124 +23,124 @@ from mumble.forms import MumbleServerForm, MumbleAdminForm, MumbleUserAdminForm from mumble.models import MumbleServer, Mumble, MumbleUser class MumbleServerAdmin(admin.ModelAdmin): - list_display = [ 'dbus', 'get_murmur_online', 'get_murmur_version' ] - search_fields = [ 'dbus' ] - ordering = [ 'dbus' ] - - form = MumbleServerForm - - def get_murmur_online( self, obj ): - return obj.online - - get_murmur_online.short_description = _('Master is running') - get_murmur_online.boolean = True - - def get_murmur_version( self, obj ): - if obj.online: - return obj.prettyversion - return "?" - - get_murmur_version.short_description = _("Server version") + list_display = [ 'dbus', 'get_murmur_online', 'get_murmur_version' ] + search_fields = [ 'dbus' ] + ordering = [ 'dbus' ] + + form = MumbleServerForm + + def get_murmur_online( self, obj ): + return obj.online + + get_murmur_online.short_description = _('Master is running') + get_murmur_online.boolean = True + + def get_murmur_version( self, obj ): + if obj.online: + return obj.prettyversion + return "?" + + get_murmur_version.short_description = _("Server version") class MumbleAdmin(admin.ModelAdmin): - """ Specification for the "Server administration" admin section. """ - - list_display = [ 'name', 'srvid', 'get_addr', 'get_port', 'get_murmur_online', 'get_booted', - 'get_is_public', 'get_users_regged', 'get_users_online', 'get_channel_count' ]; - list_filter = [ 'addr', 'server' ]; - search_fields = [ 'name', 'addr', 'port' ]; - ordering = [ 'name' ]; - form = MumbleAdminForm; - - def get_murmur_online( self, obj ): - return obj.server.online - - get_murmur_online.short_description = _('Master is running') - get_murmur_online.boolean = True - - def get_addr( self, obj ): - if not obj.addr: - return "*" - return obj.addr - - get_addr.short_description = _('Server Address') - - def get_port( self, obj ): - if not obj.port: - return '< %d >' % (obj.server.defaultPort + obj.srvid - 1) - return obj.port - - get_port.short_description = _('Server Port') - - def get_booted( self, obj ): - return obj.booted - - get_booted.short_description = _('Instance is running') - 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' ); + """ Specification for the "Server administration" admin section. """ + + list_display = [ 'name', 'srvid', 'get_addr', 'get_port', 'get_murmur_online', 'get_booted', + 'get_is_public', 'get_users_regged', 'get_users_online', 'get_channel_count' ] + list_filter = [ 'addr', 'server' ] + search_fields = [ 'name', 'addr', 'port' ] + ordering = [ 'name' ] + form = MumbleAdminForm + + def get_murmur_online( self, obj ): + return obj.server.online + + get_murmur_online.short_description = _('Master is running') + get_murmur_online.boolean = True + + def get_addr( self, obj ): + if not obj.addr: + return "*" + return obj.addr + + get_addr.short_description = _('Server Address') + + def get_port( self, obj ): + if not obj.port: + return '< %d >' % (obj.server.defaultPort + obj.srvid - 1) + return obj.port + + get_port.short_description = _('Server Port') + + def get_booted( self, obj ): + return obj.booted + + get_booted.short_description = _('Instance is running') + 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', 'mumbleid', 'get_acl_admin' ]; - list_filter = [ 'server' ]; - search_fields = [ 'owner__username', 'name' ]; - ordering = [ 'owner__username' ]; - - form = MumbleUserAdminForm - - def get_acl_admin( self, obj ): - if obj.server.booted: - return obj.aclAdmin - return None - - get_acl_admin.short_description = _('Admin on root channel') - get_acl_admin.boolean = True - - -admin.site.register( MumbleServer, MumbleServerAdmin ); -admin.site.register( Mumble, MumbleAdmin ); -admin.site.register( MumbleUser, MumbleUserAdmin ); + """ Specification for the "Registered users" admin section. """ + + list_display = [ 'owner', 'server', 'name', 'mumbleid', 'get_acl_admin' ] + list_filter = [ 'server' ] + search_fields = [ 'owner__username', 'name' ] + ordering = [ 'owner__username' ] + + form = MumbleUserAdminForm + + def get_acl_admin( self, obj ): + if obj.server.booted: + return obj.aclAdmin + return None + + get_acl_admin.short_description = _('Admin on root channel') + get_acl_admin.boolean = True + + +admin.site.register( MumbleServer, MumbleServerAdmin ) +admin.site.register( Mumble, MumbleAdmin ) +admin.site.register( MumbleUser, MumbleUserAdmin ) diff --git a/pyweb/mumble/forms.py b/pyweb/mumble/forms.py index 67da798..c803adc 100644 --- a/pyweb/mumble/forms.py +++ b/pyweb/mumble/forms.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -17,309 +18,309 @@ import socket import re -from django import forms -from django.conf import settings -from django.forms import Form, ModelForm -from django.utils.translation import ugettext_lazy as _ +from django import forms +from django.conf import settings +from django.forms import Form, ModelForm +from django.utils.translation import ugettext_lazy as _ -from mumble.models import MumbleServer, Mumble, MumbleUser +from mumble.models import MumbleServer, Mumble, MumbleUser class PropertyModelForm( ModelForm ): - """ ModelForm that gets/sets fields that are not within the model's - fields as model attributes. Necessary to get forms that manipulate - properties. - """ - - def __init__( self, *args, **kwargs ): - ModelForm.__init__( self, *args, **kwargs ); - - if self.instance: - instfields = self.instance._meta.get_all_field_names() - for fldname in self.fields: - if fldname in instfields: - continue - self.fields[fldname].initial = getattr( self.instance, fldname ) - prop = getattr( self.instance.__class__, fldname ) - if prop.__doc__: - self.fields[fldname].label = _(prop.__doc__) - - def save( self, commit=True ): - inst = ModelForm.save( self, commit=commit ) - - if commit: - self.save_to_model( inst ) - else: - # Update when the model has been saved. - from django.db.models import signals - self._update_inst = inst - signals.post_save.connect( self.save_listener, sender=inst.__class__ ) - - return inst - - def save_listener( self, **kwargs ): - if kwargs['instance'] is self._update_inst: - self.save_to_model( self._update_inst ) - - def save_to_model( self, inst ): - instfields = inst._meta.get_all_field_names() - - for fldname in self.fields: - if fldname not in instfields: - setattr( inst, fldname, self.cleaned_data[fldname] ) + """ ModelForm that gets/sets fields that are not within the model's + fields as model attributes. Necessary to get forms that manipulate + properties. + """ + + def __init__( self, *args, **kwargs ): + ModelForm.__init__( self, *args, **kwargs ) + + if self.instance: + instfields = self.instance._meta.get_all_field_names() + for fldname in self.fields: + if fldname in instfields: + continue + self.fields[fldname].initial = getattr( self.instance, fldname ) + prop = getattr( self.instance.__class__, fldname ) + if prop.__doc__: + self.fields[fldname].label = _(prop.__doc__) + + def save( self, commit=True ): + inst = ModelForm.save( self, commit=commit ) + + if commit: + self.save_to_model( inst ) + else: + # Update when the model has been saved. + from django.db.models import signals + self._update_inst = inst + signals.post_save.connect( self.save_listener, sender=inst.__class__ ) + + return inst + + def save_listener( self, **kwargs ): + if kwargs['instance'] is self._update_inst: + self.save_to_model( self._update_inst ) + + def save_to_model( self, inst ): + instfields = inst._meta.get_all_field_names() + + for fldname in self.fields: + if fldname not in instfields: + setattr( inst, fldname, self.cleaned_data[fldname] ) class MumbleForm( PropertyModelForm ): - """ The Mumble Server admin form that allows to configure settings which do - not necessarily have to be reserved to the server hoster. - - Server hosters are expected to use the Django admin application instead, - where everything can be configured freely. - """ - - url = forms.CharField( required=False ) - motd = forms.CharField( required=False, widget=forms.Textarea ) - passwd = forms.CharField( required=False, help_text=_( - "Password required to join. Leave empty for public servers.") ) - supw = forms.CharField( required=False, widget=forms.PasswordInput ) - obfsc = forms.BooleanField( required=False, help_text=_( - "If on, IP adresses of the clients are not logged.") ) - player = forms.CharField( required=False ) - channel = forms.CharField( required=False ) - defchan = forms.TypedChoiceField( choices=(), coerce=int, required=False ) - timeout = forms.IntegerField( required=False ) - certreq = forms.BooleanField( required=False ) - textlen = forms.IntegerField( required=False ) - html = forms.BooleanField( required=False ) - - def __init__( self, *args, **kwargs ): - PropertyModelForm.__init__( self, *args, **kwargs ) - - # Populate the `default channel' field's choices - choices = [ ('', '----------') ] - - if self.instance and self.instance.srvid is not None: - if self.instance.booted: - def add_item( item, level ): - if item.is_server or item.is_channel: - choices.append( ( item.chanid, ( "-"*level + " " + item.name ) ) ) - - self.instance.rootchan.visit(add_item) - else: - current = self.instance.defchan - if current is not None: - choices.append( ( current, "Current value: %d" % current ) ) - self.fields['defchan'].choices = choices - - class Meta: - model = Mumble; - fields = ['name']; + """ The Mumble Server admin form that allows to configure settings which do + not necessarily have to be reserved to the server hoster. + + Server hosters are expected to use the Django admin application instead, + where everything can be configured freely. + """ + + url = forms.CharField( required=False ) + motd = forms.CharField( required=False, widget=forms.Textarea ) + passwd = forms.CharField( required=False, help_text=_( + "Password required to join. Leave empty for public servers.") ) + supw = forms.CharField( required=False, widget=forms.PasswordInput ) + obfsc = forms.BooleanField( required=False, help_text=_( + "If on, IP adresses of the clients are not logged.") ) + player = forms.CharField( required=False ) + channel = forms.CharField( required=False ) + defchan = forms.TypedChoiceField( choices=(), coerce=int, required=False ) + timeout = forms.IntegerField( required=False ) + certreq = forms.BooleanField( required=False ) + textlen = forms.IntegerField( required=False ) + html = forms.BooleanField( required=False ) + + def __init__( self, *args, **kwargs ): + PropertyModelForm.__init__( self, *args, **kwargs ) + + # Populate the `default channel' field's choices + choices = [ ('', '----------') ] + + if self.instance and self.instance.srvid is not None: + if self.instance.booted: + def add_item( item, level ): + if item.is_server or item.is_channel: + choices.append( ( item.chanid, ( "-"*level + " " + item.name ) ) ) + + self.instance.rootchan.visit(add_item) + else: + current = self.instance.defchan + if current is not None: + choices.append( ( current, "Current value: %d" % current ) ) + self.fields['defchan'].choices = choices + + class Meta: + model = Mumble + fields = ['name'] class MumbleAdminForm( MumbleForm ): - """ A Mumble Server admin form intended to be used by the server hoster. """ - - users = forms.IntegerField( required=False ) - bwidth = forms.IntegerField( required=False ) - sslcrt = forms.CharField( required=False, widget=forms.Textarea ) - sslkey = forms.CharField( required=False, widget=forms.Textarea ) - booted = forms.BooleanField( required=False ) - autoboot = forms.BooleanField( required=False ) - bonjour = forms.BooleanField( required=False ) - - class Meta: - fields = None - exclude = None - - def clean_port( self ): - """ Check if the port number is valid. """ - - port = self.cleaned_data['port']; - - if port is not None and port != '': - if port < 1 or port >= 2**16: - raise forms.ValidationError( - _("Port number %(portno)d is not within the allowed range %(minrange)d - %(maxrange)d") % { - 'portno': port, - 'minrange': 1, - 'maxrange': 2**16, - }); - return port; - return None + """ A Mumble Server admin form intended to be used by the server hoster. """ + + users = forms.IntegerField( required=False ) + bwidth = forms.IntegerField( required=False ) + sslcrt = forms.CharField( required=False, widget=forms.Textarea ) + sslkey = forms.CharField( required=False, widget=forms.Textarea ) + booted = forms.BooleanField( required=False ) + autoboot = forms.BooleanField( required=False ) + bonjour = forms.BooleanField( required=False ) + + class Meta: + fields = None + exclude = None + + def clean_port( self ): + """ Check if the port number is valid. """ + + port = self.cleaned_data['port'] + + if port is not None and port != '': + if port < 1 or port >= 2**16: + raise forms.ValidationError( + _("Port number %(portno)d is not within the allowed range %(minrange)d - %(maxrange)d") % { + 'portno': port, + 'minrange': 1, + 'maxrange': 2**16, + }) + return port + return None class MumbleServerForm( ModelForm ): - defaultconf = forms.CharField( label=_("Default config"), required=False, widget=forms.Textarea ) - - def __init__( self, *args, **kwargs ): - ModelForm.__init__( self, *args, **kwargs ) - - if self.instance and self.instance.id: - if self.instance.online: - confstr = "" - conf = self.instance.defaultconf - for field in conf: - confstr += "%s: %s\n" % ( field, conf[field] ) - self.fields["defaultconf"].initial = confstr - else: - self.fields["defaultconf"].initial = _("This server is currently offline.") - - class Meta: - model = MumbleServer + defaultconf = forms.CharField( label=_("Default config"), required=False, widget=forms.Textarea ) + + def __init__( self, *args, **kwargs ): + ModelForm.__init__( self, *args, **kwargs ) + + if self.instance and self.instance.id: + if self.instance.online: + confstr = "" + conf = self.instance.defaultconf + for field in conf: + confstr += "%s: %s\n" % ( field, conf[field] ) + self.fields["defaultconf"].initial = confstr + else: + self.fields["defaultconf"].initial = _("This server is currently offline.") + + class Meta: + model = MumbleServer class MumbleUserForm( ModelForm ): - """ The user registration form used to register an account. """ - - password = forms.CharField( widget=forms.PasswordInput, required=False ) - - def __init__( self, *args, **kwargs ): - ModelForm.__init__( self, *args, **kwargs ); - self.server = None; - - def clean_name( self ): - """ Check if the desired name is forbidden or taken. """ - - name = self.cleaned_data['name']; - - if self.server is None: - raise AttributeError( "You need to set the form's server attribute to the server instance " - "for validation to work." ); - - if self.server.player and re.compile( self.server.player ).match( name ) is None: - raise forms.ValidationError( _( "That name is forbidden by the server." ) ); - - if not self.instance.id and len( self.server.ctl.getRegisteredPlayers( self.server.srvid, name ) ) > 0: - raise forms.ValidationError( _( "Another player already registered that name." ) ); - - return name; - - def clean_password( self ): - """ Verify a password has been given. """ - passwd = self.cleaned_data['password']; - if not passwd and ( not self.instance or self.instance.mumbleid == -1 ): - raise forms.ValidationError( _( "Cannot register player without a password!" ) ); - return passwd; - - class Meta: - model = MumbleUser; - fields = ( 'name', 'password' ); + """ The user registration form used to register an account. """ + + password = forms.CharField( widget=forms.PasswordInput, required=False ) + + def __init__( self, *args, **kwargs ): + ModelForm.__init__( self, *args, **kwargs ) + self.server = None + + def clean_name( self ): + """ Check if the desired name is forbidden or taken. """ + + name = self.cleaned_data['name'] + + if self.server is None: + raise AttributeError( "You need to set the form's server attribute to the server instance " + "for validation to work." ) + + if self.server.player and re.compile( self.server.player ).match( name ) is None: + raise forms.ValidationError( _( "That name is forbidden by the server." ) ) + + if not self.instance.id and len( self.server.ctl.getRegisteredPlayers( self.server.srvid, name ) ) > 0: + raise forms.ValidationError( _( "Another player already registered that name." ) ) + + return name + + def clean_password( self ): + """ Verify a password has been given. """ + passwd = self.cleaned_data['password'] + if not passwd and ( not self.instance or self.instance.mumbleid == -1 ): + raise forms.ValidationError( _( "Cannot register player without a password!" ) ) + return passwd + + class Meta: + model = MumbleUser + fields = ( 'name', 'password' ) class MumbleUserPasswordForm( MumbleUserForm ): - """ The user registration form used to register an account on a private server in protected mode. """ - - serverpw = forms.CharField( - label=_('Server Password'), - help_text=_('This server is private and protected mode is active. Please enter the server password.'), - widget=forms.PasswordInput(render_value=False) - ); - - def clean_serverpw( self ): - """ Validate the password """ - serverpw = self.cleaned_data['serverpw']; - if self.server.passwd != serverpw: - raise forms.ValidationError( _( "The password you entered is incorrect." ) ); - return serverpw; - - def clean( self ): - """ prevent save() from trying to store the password in the Model instance. """ - # clean() will be called after clean_serverpw(), so it has already been validated here. - if 'serverpw' in self.cleaned_data: - del( self.cleaned_data['serverpw'] ); - return self.cleaned_data; + """ The user registration form used to register an account on a private server in protected mode. """ + + serverpw = forms.CharField( + label=_('Server Password'), + help_text=_('This server is private and protected mode is active. Please enter the server password.'), + widget=forms.PasswordInput(render_value=False) + ) + + def clean_serverpw( self ): + """ Validate the password """ + serverpw = self.cleaned_data['serverpw'] + if self.server.passwd != serverpw: + raise forms.ValidationError( _( "The password you entered is incorrect." ) ) + return serverpw + + def clean( self ): + """ prevent save() from trying to store the password in the Model instance. """ + # clean() will be called after clean_serverpw(), so it has already been validated here. + if 'serverpw' in self.cleaned_data: + del( self.cleaned_data['serverpw'] ) + return self.cleaned_data class MumbleUserLinkForm( MumbleUserForm ): - """ Special registration form to either register or link an account. """ - - linkacc = forms.BooleanField( - label=_('Link account'), - help_text=_('The account already exists and belongs to me, just link it instead of creating.'), - required=False, - ); - - def __init__( self, *args, **kwargs ): - MumbleUserForm.__init__( self, *args, **kwargs ); - self.mumbleid = None; - - def clean_name( self ): - """ Check if the target account exists in Murmur. """ - if 'linkacc' not in self.data: - return MumbleUserForm.clean_name( self ); - - # Check if user exists - name = self.cleaned_data['name']; - - if len( self.server.ctl.getRegisteredPlayers( self.server.srvid, name ) ) != 1: - raise forms.ValidationError( _( "No such user found." ) ); - - return name; - - def clean_password( self ): - """ Verify that the password is correct. """ - if 'linkacc' not in self.data: - return MumbleUserForm.clean_password( self ); - - if 'name' not in self.cleaned_data: - # keep clean() from trying to find a user that CAN'T exist - self.mumbleid = -10; - return ''; - - # Validate password with Murmur - passwd = self.cleaned_data['password']; - - self.mumbleid = self.server.ctl.verifyPassword( self.server.srvid, self.cleaned_data['name'], passwd ) - if self.mumbleid <= 0: - raise forms.ValidationError( _( "The password you entered is incorrect." ) ); - - return passwd; - - def clean( self ): - """ Create the MumbleUser instance to save in. """ - if 'linkacc' not in self.data or self.mumbleid <= 0: - return self.cleaned_data; - - try: - m_user = MumbleUser.objects.get( server=self.server, mumbleid=self.mumbleid ); - except MumbleUser.DoesNotExist: - m_user = MumbleUser( server=self.server, name=self.cleaned_data['name'], mumbleid=self.mumbleid ); - m_user.save( dontConfigureMurmur=True ); - else: - if m_user.owner is not None: - raise forms.ValidationError( _( "That account belongs to someone else." ) ); - - if m_user.getAdmin() and not settings.ALLOW_ACCOUNT_LINKING_ADMINS: - raise forms.ValidationError( _( "Linking Admin accounts is not allowed." ) ); - self.instance = m_user; - - return self.cleaned_data; + """ Special registration form to either register or link an account. """ + + linkacc = forms.BooleanField( + label=_('Link account'), + help_text=_('The account already exists and belongs to me, just link it instead of creating.'), + required=False, + ) + + def __init__( self, *args, **kwargs ): + MumbleUserForm.__init__( self, *args, **kwargs ) + self.mumbleid = None + + def clean_name( self ): + """ Check if the target account exists in Murmur. """ + if 'linkacc' not in self.data: + return MumbleUserForm.clean_name( self ) + + # Check if user exists + name = self.cleaned_data['name'] + + if len( self.server.ctl.getRegisteredPlayers( self.server.srvid, name ) ) != 1: + raise forms.ValidationError( _( "No such user found." ) ) + + return name + + def clean_password( self ): + """ Verify that the password is correct. """ + if 'linkacc' not in self.data: + return MumbleUserForm.clean_password( self ) + + if 'name' not in self.cleaned_data: + # keep clean() from trying to find a user that CAN'T exist + self.mumbleid = -10 + return '' + + # Validate password with Murmur + passwd = self.cleaned_data['password'] + + self.mumbleid = self.server.ctl.verifyPassword( self.server.srvid, self.cleaned_data['name'], passwd ) + if self.mumbleid <= 0: + raise forms.ValidationError( _( "The password you entered is incorrect." ) ) + + return passwd + + def clean( self ): + """ Create the MumbleUser instance to save in. """ + if 'linkacc' not in self.data or self.mumbleid <= 0: + return self.cleaned_data + + try: + m_user = MumbleUser.objects.get( server=self.server, mumbleid=self.mumbleid ) + except MumbleUser.DoesNotExist: + m_user = MumbleUser( server=self.server, name=self.cleaned_data['name'], mumbleid=self.mumbleid ) + m_user.save( dontConfigureMurmur=True ) + else: + if m_user.owner is not None: + raise forms.ValidationError( _( "That account belongs to someone else." ) ) + + if m_user.getAdmin() and not settings.ALLOW_ACCOUNT_LINKING_ADMINS: + raise forms.ValidationError( _( "Linking Admin accounts is not allowed." ) ) + self.instance = m_user + + return self.cleaned_data class MumbleUserAdminForm( PropertyModelForm ): - aclAdmin = forms.BooleanField( required=False ); - password = forms.CharField( widget=forms.PasswordInput, required=False ) - - def clean_password( self ): - """ Verify a password has been given. """ - passwd = self.cleaned_data['password']; - if not passwd and ( not self.instance or self.instance.mumbleid == -1 ): - raise forms.ValidationError( _( "Cannot register player without a password!" ) ); - return passwd; - - class Meta: - model = Mumble; + aclAdmin = forms.BooleanField( required=False ) + password = forms.CharField( widget=forms.PasswordInput, required=False ) + + def clean_password( self ): + """ Verify a password has been given. """ + passwd = self.cleaned_data['password'] + if not passwd and ( not self.instance or self.instance.mumbleid == -1 ): + raise forms.ValidationError( _( "Cannot register player without a password!" ) ) + return passwd + + class Meta: + model = Mumble class MumbleKickForm( Form ): - session = forms.IntegerField(); - ban = forms.BooleanField( required=False ); - reason = forms.CharField( required=False ); + session = forms.IntegerField() + ban = forms.BooleanField( required=False ) + reason = forms.CharField( required=False ) class MumbleTextureForm( Form ): - """ The form used to upload a new image to be set as texture. """ - usegravatar = forms.BooleanField( required=False, label=_("Use my Gravatar as my Texture") ); - texturefile = forms.ImageField( required=False, label=_("User Texture") ); + """ The form used to upload a new image to be set as texture. """ + usegravatar = forms.BooleanField( required=False, label=_("Use my Gravatar as my Texture") ) + texturefile = forms.ImageField( required=False, label=_("User Texture") ) diff --git a/pyweb/mumble/management/__init__.py b/pyweb/mumble/management/__init__.py index 49de3a9..6e01498 100644 --- a/pyweb/mumble/management/__init__.py +++ b/pyweb/mumble/management/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -14,24 +15,24 @@ * GNU General Public License for more details. """ -from shutil import copy, move -from os.path import exists, join +from shutil import copy, move +from os.path import exists, join -from django.conf import settings -from django.db import connection -from django.db.models import signals +from django.conf import settings +from django.db import connection +from django.db.models import signals -from mumble import models +from mumble import models -from update_schema import update_schema -from server_detect import find_existing_instances +from update_schema import update_schema +from server_detect import find_existing_instances if settings.DATABASE_ENGINE == "sqlite3": - # Move the DB to the db subdirectory if necessary. - oldpath = join( settings.MUMBLE_DJANGO_ROOT, "mumble-django.db3" ) - if not exists( settings.DATABASE_NAME ) and exists( oldpath ): - move( oldpath, settings.DATABASE_NAME ) + # Move the DB to the db subdirectory if necessary. + oldpath = join( settings.MUMBLE_DJANGO_ROOT, "mumble-django.db3" ) + if not exists( settings.DATABASE_NAME ) and exists( oldpath ): + move( oldpath, settings.DATABASE_NAME ) cursor = connection.cursor() @@ -39,18 +40,18 @@ cursor = connection.cursor() tablename = models.Mumble._meta.db_table if tablename in connection.introspection.get_table_list(cursor): - fields = connection.introspection.get_table_description(cursor, tablename) - uptodate = "server_id" in [ entry[0] for entry in fields ] + fields = connection.introspection.get_table_description(cursor, tablename) + uptodate = "server_id" in [ entry[0] for entry in fields ] else: - # Table doesn't yet exist, so syncdb will create it properly - uptodate = True + # Table doesn't yet exist, so syncdb will create it properly + uptodate = True if not uptodate: - if settings.DATABASE_ENGINE == "sqlite3": - # backup the db before the conversion. - copy( settings.DATABASE_NAME, settings.DATABASE_NAME+".bak" ) - signals.post_syncdb.connect( update_schema, sender=models ); + if settings.DATABASE_ENGINE == "sqlite3": + # backup the db before the conversion. + copy( settings.DATABASE_NAME, settings.DATABASE_NAME+".bak" ) + signals.post_syncdb.connect( update_schema, sender=models ) else: - signals.post_syncdb.connect( find_existing_instances, sender=models ); + signals.post_syncdb.connect( find_existing_instances, sender=models ) diff --git a/pyweb/mumble/management/commands/__init__.py b/pyweb/mumble/management/commands/__init__.py index 6ceaf67..93ba04c 100644 --- a/pyweb/mumble/management/commands/__init__.py +++ b/pyweb/mumble/management/commands/__init__.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler diff --git a/pyweb/mumble/management/commands/checkenv.py b/pyweb/mumble/management/commands/checkenv.py index c65baf4..f0ab5f0 100644 --- a/pyweb/mumble/management/commands/checkenv.py +++ b/pyweb/mumble/management/commands/checkenv.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -16,184 +17,184 @@ import os -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 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 +from mumble.models import Mumble class TestFailed( Exception ): - pass; + pass class Command( BaseCommand ): - help = "Run a few tests on Mumble-Django's setup." - - def handle(self, **options): - try: - import Ice - except ImportError: - pass - else: - 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.ctl - except Exception, err: - raise TestFailed( - "Connecting to Murmur `%s` (%s) failed: %s" % ( mumble.name, mumble.server, 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 ]"; + help = "Run a few tests on Mumble-Django's setup." + + def handle(self, **options): + try: + import Ice + except ImportError: + pass + else: + 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.ctl + except Exception, err: + raise TestFailed( + "Connecting to Murmur `%s` (%s) failed: %s" % ( mumble.name, mumble.server, 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 ]" diff --git a/pyweb/mumble/management/commands/getslice.py b/pyweb/mumble/management/commands/getslice.py index 9c23ef1..fbdfee5 100644 --- a/pyweb/mumble/management/commands/getslice.py +++ b/pyweb/mumble/management/commands/getslice.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -17,41 +18,41 @@ import Ice, IcePy, os, getpass from sys import stderr -from django.core.management.base import BaseCommand +from django.core.management.base import BaseCommand -from mumble.models import MumbleServer +from mumble.models import MumbleServer class Command( BaseCommand ): - help = "Check if the known servers support getSlice." - - def handle(self, **options): - prop = Ice.createProperties([]) - prop.setProperty("Ice.ImplicitContext", "Shared") - - idd = Ice.InitializationData() - idd.properties = prop - - ice = Ice.initialize(idd) - - for serv in MumbleServer.objects.all(): - print >>stderr, "Probing server at '%s'..." % serv.dbus - - if serv.secret: - ice.getImplicitContext().put( "secret", serv.secret.encode("utf-8") ) - - prx = ice.stringToProxy( serv.dbus.encode("utf-8") ) - - # Try loading the Slice from Murmur directly via its getSlice method. - try: - slice = IcePy.Operation( 'getSlice', - Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, - True, (), (), (), IcePy._t_string, () - ).invoke(prx, ((), None)) - except TypeError, err: - print >>stderr, " Received TypeError:", err - print >>stderr, " It seems your version of IcePy is incompatible." - except Ice.OperationNotExistException: - print >>stderr, " Your version of Murmur does not support getSlice." - else: - print slice - print >>stderr, " Successfully received the slice (length: %d bytes.)" % len(slice) + help = "Check if the known servers support getSlice." + + def handle(self, **options): + prop = Ice.createProperties([]) + prop.setProperty("Ice.ImplicitContext", "Shared") + + idd = Ice.InitializationData() + idd.properties = prop + + ice = Ice.initialize(idd) + + for serv in MumbleServer.objects.all(): + print >>stderr, "Probing server at '%s'..." % serv.dbus + + if serv.secret: + ice.getImplicitContext().put( "secret", serv.secret.encode("utf-8") ) + + prx = ice.stringToProxy( serv.dbus.encode("utf-8") ) + + # Try loading the Slice from Murmur directly via its getSlice method. + try: + slice = IcePy.Operation( 'getSlice', + Ice.OperationMode.Idempotent, Ice.OperationMode.Idempotent, + True, (), (), (), IcePy._t_string, () + ).invoke(prx, ((), None)) + except TypeError, err: + print >>stderr, " Received TypeError:", err + print >>stderr, " It seems your version of IcePy is incompatible." + except Ice.OperationNotExistException: + print >>stderr, " Your version of Murmur does not support getSlice." + else: + print slice + print >>stderr, " Successfully received the slice (length: %d bytes.)" % len(slice) diff --git a/pyweb/mumble/management/commands/mmrunserver.py b/pyweb/mumble/management/commands/mmrunserver.py index b47bfe7..c5caa0c 100644 --- a/pyweb/mumble/management/commands/mmrunserver.py +++ b/pyweb/mumble/management/commands/mmrunserver.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -14,8 +15,8 @@ * GNU General Public License for more details. """ -from django.core.management.commands.runserver import Command as OrigCommand -from mumble.murmurenvutils import MumbleCommandWrapper +from django.core.management.commands.runserver import Command as OrigCommand +from mumble.murmurenvutils import MumbleCommandWrapper class Command( MumbleCommandWrapper, OrigCommand ): - pass + pass diff --git a/pyweb/mumble/management/commands/mmshell.py b/pyweb/mumble/management/commands/mmshell.py index fcf35c9..1b8d3b0 100644 --- a/pyweb/mumble/management/commands/mmshell.py +++ b/pyweb/mumble/management/commands/mmshell.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -14,8 +15,8 @@ * GNU General Public License for more details. """ -from django.core.management.commands.shell import Command as OrigCommand -from mumble.murmurenvutils import MumbleCommandWrapper_noargs +from django.core.management.commands.shell import Command as OrigCommand +from mumble.murmurenvutils import MumbleCommandWrapper_noargs class Command( MumbleCommandWrapper_noargs, OrigCommand ): - pass + pass diff --git a/pyweb/mumble/management/commands/mmsyncdb.py b/pyweb/mumble/management/commands/mmsyncdb.py index 2611198..23e538f 100644 --- a/pyweb/mumble/management/commands/mmsyncdb.py +++ b/pyweb/mumble/management/commands/mmsyncdb.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -14,8 +15,8 @@ * GNU General Public License for more details. """ -from django.core.management.commands.syncdb import Command as OrigCommand -from mumble.murmurenvutils import MumbleCommandWrapper_noargs +from django.core.management.commands.syncdb import Command as OrigCommand +from mumble.murmurenvutils import MumbleCommandWrapper_noargs class Command( MumbleCommandWrapper_noargs, OrigCommand ): - pass + pass diff --git a/pyweb/mumble/management/server_detect.py b/pyweb/mumble/management/server_detect.py index 769876e..5438741 100644 --- a/pyweb/mumble/management/server_detect.py +++ b/pyweb/mumble/management/server_detect.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -16,154 +17,154 @@ import os, getpass -from django.db import DatabaseError -from django.conf import settings +from django.db import DatabaseError +from django.conf import settings -from mumble.models import MumbleServer, Mumble -from mumble.mctl import MumbleCtlBase +from mumble.models import MumbleServer, Mumble +from mumble.mctl import MumbleCtlBase def find_in_dicts( keys, conf, default, valueIfNotFound=None ): - if not isinstance( keys, tuple ): - keys = ( keys, ); + if not isinstance( keys, tuple ): + keys = ( keys, ) - for keyword in keys: - if keyword in conf: - return conf[keyword]; + 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]; + for keyword in keys: + keyword = keyword.lower() + if keyword in default: + return default[keyword] - return valueIfNotFound; + 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"; - - icesecret = getpass.getpass("Please enter the Ice secret (if any): "); - - try: - ctl = MumbleCtlBase.newInstance( dbusName, settings.SLICE, icesecret ); - 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(); - - try: - meta = MumbleServer.objects.get( dbus=dbusName ); - except MumbleServer.DoesNotExist: - meta = MumbleServer( dbus=dbusName ); - finally: - meta.secret = icesecret; - meta.save(); - - 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 = Mumble.objects.get( server=meta, srvid=id ); - except Mumble.DoesNotExist: - values = { - "server": meta, - "srvid": id, - } - - if v: - print "Found new Murmur instance %d on bus '%s'... " % ( id, dbusName ) - - # now create a model for the record set. - instance = Mumble( **values ); - else: - if v: - print "Syncing Murmur instance %d: '%s'... " % ( instance.id, instance.name ) - - try: - instance.configureFromMurmur(); - except DatabaseError, err: - try: - # Find instances with the same address/port - dup = Mumble.objects.get( addr=instance.addr, port=instance.port ) - except Mumble.DoesNotExist: - # None exist - this must've been something else. - print "Server ID / Name: %d / %s" % ( instance.srvid, instance.name ) - raise err - else: - print "ERROR: There is already another server instance registered" - print " on the same address and port." - print " -------------" - print " New Server ID:", instance.srvid, - print " New Server Name:", instance.name - print " Address:", instance.addr - print " Port:", instance.port - print " Connection string:", instance.server.dbus - print " -------------" - print " Duplicate Server ID:", dup.srvid, - print "Duplicate Server Name:", dup.name - print " Address:", dup.addr - print " Port:", dup.port - print " Connection string:", dup.server.dbus - return False - except Exception, err: - print "Server ID / Name: %d / %s" % ( instance.srvid, instance.name ) - raise err - - # 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: - print "This server is not running, can't sync players."; - - if v > 1: - print "Successfully finished Servers and Players detection."; - return True; + 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" + + icesecret = getpass.getpass("Please enter the Ice secret (if any): ") + + try: + ctl = MumbleCtlBase.newInstance( dbusName, settings.SLICE, icesecret ) + 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() + + try: + meta = MumbleServer.objects.get( dbus=dbusName ) + except MumbleServer.DoesNotExist: + meta = MumbleServer( dbus=dbusName ) + finally: + meta.secret = icesecret + meta.save() + + 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 = Mumble.objects.get( server=meta, srvid=id ) + except Mumble.DoesNotExist: + values = { + "server": meta, + "srvid": id, + } + + if v: + print "Found new Murmur instance %d on bus '%s'... " % ( id, dbusName ) + + # now create a model for the record set. + instance = Mumble( **values ) + else: + if v: + print "Syncing Murmur instance %d: '%s'... " % ( instance.id, instance.name ) + + try: + instance.configureFromMurmur() + except DatabaseError, err: + try: + # Find instances with the same address/port + dup = Mumble.objects.get( addr=instance.addr, port=instance.port ) + except Mumble.DoesNotExist: + # None exist - this must've been something else. + print "Server ID / Name: %d / %s" % ( instance.srvid, instance.name ) + raise err + else: + print "ERROR: There is already another server instance registered" + print " on the same address and port." + print " -------------" + print " New Server ID:", instance.srvid, + print " New Server Name:", instance.name + print " Address:", instance.addr + print " Port:", instance.port + print " Connection string:", instance.server.dbus + print " -------------" + print " Duplicate Server ID:", dup.srvid, + print "Duplicate Server Name:", dup.name + print " Address:", dup.addr + print " Port:", dup.port + print " Connection string:", dup.server.dbus + return False + except Exception, err: + print "Server ID / Name: %d / %s" % ( instance.srvid, instance.name ) + raise err + + # 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: + print "This server is not running, can't sync players." + + if v > 1: + print "Successfully finished Servers and Players detection." + return True diff --git a/pyweb/mumble/management/update_schema.py b/pyweb/mumble/management/update_schema.py index edf62b2..6ec583d 100644 --- a/pyweb/mumble/management/update_schema.py +++ b/pyweb/mumble/management/update_schema.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2010, Michael "Svedrin" Ziegler @@ -15,62 +16,62 @@ """ import os -from os.path import join +from os.path import join -from django.db import connection, transaction -from django.db.models import signals -from django.conf import settings +from django.db import connection, transaction +from django.db.models import signals +from django.conf import settings -from mumble import models +from mumble import models from mumble.management.server_detect import find_existing_instances def update_schema( **kwargs ): - if "verbosity" in kwargs: - v = kwargs['verbosity']; - else: - v = 1; - - if v: - print "Migrating Database schema for Mumble-Django 2.0 now." - - scriptdir = join( settings.CONVERSIONSQL_ROOT, { - 'postgresql_psycopg2': 'pgsql', - 'postgresql': 'pgsql', - 'mysql': 'mysql', - 'sqlite3': 'sqlite', - }[settings.DATABASE_ENGINE] ) - - if v > 1: - print "Reading migration scripts for %s from '%s'" % ( settings.DATABASE_ENGINE, scriptdir ) - - scripts = [ filename for filename in os.listdir( scriptdir ) if filename.endswith( ".sql" ) ] - scripts.sort() - - for filename in scripts: - cursor = connection.cursor() - - scriptpath = os.path.join( scriptdir, filename ) - scriptfile = open( scriptpath, "r" ) - try: - if v > 1: - print "Running migration script '%s'..." % scriptpath - stmt = scriptfile.read() - cursor.execute( stmt ) - - except IOError, err: - print "Error reading file '%s':" % filename - print err - - except cursor.db.connection.Error, err: - print "Error executing file '%s':" % filename - print err - - finally: - scriptfile.close() - cursor.close() - - if v: - print "Database migration finished successfully." - - find_existing_instances( **kwargs ) + if "verbosity" in kwargs: + v = kwargs['verbosity'] + else: + v = 1 + + if v: + print "Migrating Database schema for Mumble-Django 2.0 now." + + scriptdir = join( settings.CONVERSIONSQL_ROOT, { + 'postgresql_psycopg2': 'pgsql', + 'postgresql': 'pgsql', + 'mysql': 'mysql', + 'sqlite3': 'sqlite', + }[settings.DATABASE_ENGINE] ) + + if v > 1: + print "Reading migration scripts for %s from '%s'" % ( settings.DATABASE_ENGINE, scriptdir ) + + scripts = [ filename for filename in os.listdir( scriptdir ) if filename.endswith( ".sql" ) ] + scripts.sort() + + for filename in scripts: + cursor = connection.cursor() + + scriptpath = os.path.join( scriptdir, filename ) + scriptfile = open( scriptpath, "r" ) + try: + if v > 1: + print "Running migration script '%s'..." % scriptpath + stmt = scriptfile.read() + cursor.execute( stmt ) + + except IOError, err: + print "Error reading file '%s':" % filename + print err + + except cursor.db.connection.Error, err: + print "Error executing file '%s':" % filename + print err + + finally: + scriptfile.close() + cursor.close() + + if v: + print "Database migration finished successfully." + + find_existing_instances( **kwargs ) diff --git a/pyweb/mumble/mctl.py b/pyweb/mumble/mctl.py index d7f030b..72750e5 100644 --- a/pyweb/mumble/mctl.py +++ b/pyweb/mumble/mctl.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009, withgod @@ -18,41 +19,41 @@ import re class MumbleCtlBase(object): - """ This class defines the base interface that the Mumble model expects. """ - - cache = {}; - - @staticmethod - def newInstance( connstring, slicefile=None, icesecret=None ): - """ Create a new CTL object for the given connstring. - - Optional parameters are the path to the slice file and the - Ice secret necessary to authenticate to Murmur. - - The path can be omitted only if using DBus or running Murmur - 1.2.3 or later, which exports a getSlice method to retrieve - the Slice from. - """ - - # 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, icesecret ) - - MumbleCtlBase.cache[connstring] = ctl; - return ctl; - - @staticmethod - def clearCache(): - MumbleCtlBase.cache = {}; + """ This class defines the base interface that the Mumble model expects. """ + + cache = {} + + @staticmethod + def newInstance( connstring, slicefile=None, icesecret=None ): + """ Create a new CTL object for the given connstring. + + Optional parameters are the path to the slice file and the + Ice secret necessary to authenticate to Murmur. + + The path can be omitted only if using DBus or running Murmur + 1.2.3 or later, which exports a getSlice method to retrieve + the Slice from. + """ + + # 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, icesecret ) + + MumbleCtlBase.cache[connstring] = ctl + return ctl + + @staticmethod + def clearCache(): + MumbleCtlBase.cache = {} diff --git a/pyweb/mumble/mmobjects.py b/pyweb/mumble/mmobjects.py index d0aebd7..ed0a5bf 100644 --- a/pyweb/mumble/mmobjects.py +++ b/pyweb/mumble/mmobjects.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -17,334 +18,334 @@ import socket import datetime import re -from time import time +from time import time -from django.utils.http import urlquote -from django.conf import settings +from django.utils.http import urlquote +from django.conf import settings def cmp_channels( left, rite ): - """ Compare two channels, first by position, and if that equals, by name. """ - if hasattr( left, "position" ) and hasattr( rite, "position" ): - byorder = cmp( left.position, rite.position ); - if byorder != 0: - return byorder; - return cmp_names( left, rite ); + """ Compare two channels, first by position, and if that equals, by name. """ + if hasattr( left, "position" ) and hasattr( rite, "position" ): + byorder = cmp( left.position, rite.position ) + if byorder != 0: + return byorder + return cmp_names( left, rite ) def cmp_names( left, rite ): - """ Compare two objects by their name property. """ - return cmp( left.name, rite.name ); + """ 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 ); - - - 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 '' % ( 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_channels ); - 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 - """ - from urlparse import urlunsplit - versionstr = "version=%s" % self.server.prettyversion; - - if self.parent is not None: - chanlist = self.parent_channels() + [self.name]; - chanlist = [ urlquote( chan ) for chan in chanlist ]; - urlpath = "/".join( chanlist ); - else: - urlpath = ""; - - if for_user is not None: - netloc = "%s@%s" % ( for_user.name, self.server.netloc ); - return urlunsplit(( "mumble", netloc, urlpath, versionstr, "" )) - else: - return urlunsplit(( "mumble", self.server.netloc, urlpath, versionstr, "" )) - - connecturl = property( 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; - - def asMvXml( self, parentnode ): - """ Return an XML tree for this channel suitable for MumbleViewer-ng. """ - from xml.etree.cElementTree import SubElement - me = SubElement( parentnode, "item" , id=self.id, rel='channel' ) - content = SubElement( me, "content" ) - name = SubElement( content , "name" ) - name.text = self.name - - for sc in self.subchans: - sc.asMvXml(me) - for pl in self.players: - pl.asMvXml(me) - - def asMvJson( self ): - """ Return a Dict for this channel suitable for MumbleViewer-ng. """ - return { - "attributes": { - "href": self.connecturl, - "id": self.id, - "rel": "channel", - }, - "data": self.name, - "children": [ sc.asMvJson() for sc in self.subchans ] + \ - [ pl.asMvJson() for pl in self.players ], - "state": { False: "closed", True: "open" }[self.top_or_not_empty], - } + """ 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 ) + + + 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 '' % ( 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_channels ) + 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 + """ + from urlparse import urlunsplit + versionstr = "version=%s" % self.server.prettyversion + + if self.parent is not None: + chanlist = self.parent_channels() + [self.name] + chanlist = [ urlquote( chan ) for chan in chanlist ] + urlpath = "/".join( chanlist ) + else: + urlpath = "" + + if for_user is not None: + netloc = "%s@%s" % ( for_user.name, self.server.netloc ) + return urlunsplit(( "mumble", netloc, urlpath, versionstr, "" )) + else: + return urlunsplit(( "mumble", self.server.netloc, urlpath, versionstr, "" )) + + connecturl = property( 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 + + def asMvXml( self, parentnode ): + """ Return an XML tree for this channel suitable for MumbleViewer-ng. """ + from xml.etree.cElementTree import SubElement + me = SubElement( parentnode, "item" , id=self.id, rel='channel' ) + content = SubElement( me, "content" ) + name = SubElement( content , "name" ) + name.text = self.name + + for sc in self.subchans: + sc.asMvXml(me) + for pl in self.players: + pl.asMvXml(me) + + def asMvJson( self ): + """ Return a Dict for this channel suitable for MumbleViewer-ng. """ + return { + "attributes": { + "href": self.connecturl, + "id": self.id, + "rel": "channel", + }, + "data": self.name, + "children": [ sc.asMvJson() for sc in self.subchans ] + \ + [ pl.asMvJson() for pl in self.players ], + "state": { False: "closed", True: "open" }[self.top_or_not_empty], + } 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 '' % ( 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; - - def getIpAsString( self ): - """ Get the client's IPv4 or IPv6 address, in a pretty format. """ - addr = self.player_obj.address; - if max( addr[:10] ) == 0 and addr[10:12] == (255, 255): - return "%d.%d.%d.%d" % tuple( addr[12:] ); - ip6addr = [(hi << 8 | lo) for (hi, lo) in zip(addr[0::2], addr[1::2])] - # colon-separated string: - ipstr = ':'.join([ ("%x" % part) for part in ip6addr ]); - # 0:0:0 -> :: - return re.sub( "((^|:)(0:){2,})", '::', ipstr, 1 ); - - ipaddress = property( getIpAsString ); - fqdn = property( lambda self: socket.getfqdn( self.ipaddress ), - doc="The fully qualified domain name of the user's host." ); - - # 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; - - def asMvXml( self, parentnode ): - """ Return an XML node for this player suitable for MumbleViewer-ng. """ - from xml.etree.cElementTree import SubElement - me = SubElement( parentnode, "item" , id=self.id, rel='user' ) - content = SubElement( me, "content" ) - name = SubElement( content , "name" ) - name.text = self.name - - def asMvJson( self ): - """ Return a Dict for this player suitable for MumbleViewer-ng. """ - return { - "attributes": { - "id": self.id, - "rel": "user", - }, - 'data': self.name, - } + """ 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 '' % ( 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 + + def getIpAsString( self ): + """ Get the client's IPv4 or IPv6 address, in a pretty format. """ + addr = self.player_obj.address + if max( addr[:10] ) == 0 and addr[10:12] == (255, 255): + return "%d.%d.%d.%d" % tuple( addr[12:] ) + ip6addr = [(hi << 8 | lo) for (hi, lo) in zip(addr[0::2], addr[1::2])] + # colon-separated string: + ipstr = ':'.join([ ("%x" % part) for part in ip6addr ]) + # 0:0:0 -> :: + return re.sub( "((^|:)(0:){2,})", '::', ipstr, 1 ) + + ipaddress = property( getIpAsString ) + fqdn = property( lambda self: socket.getfqdn( self.ipaddress ), + doc="The fully qualified domain name of the user's host." ) + + # 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 + + def asMvXml( self, parentnode ): + """ Return an XML node for this player suitable for MumbleViewer-ng. """ + from xml.etree.cElementTree import SubElement + me = SubElement( parentnode, "item" , id=self.id, rel='user' ) + content = SubElement( me, "content" ) + name = SubElement( content , "name" ) + name.text = self.name + + def asMvJson( self ): + """ Return a Dict for this player suitable for MumbleViewer-ng. """ + return { + "attributes": { + "id": self.id, + "rel": "user", + }, + 'data': self.name, + } 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 - ); + """ 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 + ) diff --git a/pyweb/mumble/models.py b/pyweb/mumble/models.py index 0c2608c..3c1bd86 100644 --- a/pyweb/mumble/models.py +++ b/pyweb/mumble/models.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -15,762 +16,762 @@ """ import socket, Ice, re -from sys import stderr -from urllib import urlopen -from StringIO import StringIO -from PIL import Image +from sys import stderr +from urllib import urlopen +from StringIO import StringIO +from PIL import Image -from django.utils.translation import ugettext_noop, ugettext_lazy as _ +from django.utils.translation import ugettext_noop, 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 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 +from mumble.mmobjects import mmChannel, mmPlayer +from mumble.mctl import MumbleCtlBase def get_ipv46_host_by_name( hostname ): - """ Resolve the given hostname and return both its IPv4 and IPv6 address, - if applicable. Returns: { AF_INET: inet4address, AF_INET6: inet6address }. - For addresses that don't exist, the corresponding field will be None. - """ - addrinfo = socket.getaddrinfo( hostname, settings.MUMBLE_DEFAULT_PORT ) - ret = {} - for (family, socktype, proto, canonname, sockaddr) in addrinfo: - if family not in ret: - ret[family] = sockaddr[0] - return ret + """ Resolve the given hostname and return both its IPv4 and IPv6 address, + if applicable. Returns: { AF_INET: inet4address, AF_INET6: inet6address }. + For addresses that don't exist, the corresponding field will be None. + """ + addrinfo = socket.getaddrinfo( hostname, settings.MUMBLE_DEFAULT_PORT ) + ret = {} + for (family, socktype, proto, canonname, sockaddr) in addrinfo: + if family not in ret: + ret[family] = sockaddr[0] + return ret def get_ipv46_str_by_name( hostname, sep=" " ): - """ Return a space-separated string of all addresses the given hostname resolves to. """ - return sep.join( get_ipv46_host_by_name( hostname ).values() ) + """ Return a space-separated string of all addresses the given hostname resolves to. """ + return sep.join( get_ipv46_host_by_name( hostname ).values() ) def mk_config_property( field, doc="", get_coerce=None, get_none=None, set_coerce=unicode, set_none='' ): - """ Create a property for the given config field. """ - - def get_field( self ): - if self.id is not None: - val = self.getConf( field ); - if val is None or val == '': - return get_none - if callable(get_coerce): - return get_coerce( val ) - return val - return None - - def set_field( self, value ): - if value is None: - self.setConf( field, set_none ) - elif callable(set_coerce): - self.setConf( field, set_coerce(value) ) - else: - self.setConf( field, value ) - - return property( get_field, set_field, doc=doc ) + """ Create a property for the given config field. """ + + def get_field( self ): + if self.id is not None: + val = self.getConf( field ) + if val is None or val == '': + return get_none + if callable(get_coerce): + return get_coerce( val ) + return val + return None + + def set_field( self, value ): + if value is None: + self.setConf( field, set_none ) + elif callable(set_coerce): + self.setConf( field, set_coerce(value) ) + else: + self.setConf( field, value ) + + return property( get_field, set_field, doc=doc ) def mk_config_bool_property( field, doc="" ): - return mk_config_property( field, doc=doc, - get_coerce = lambda value: value == "true", - set_coerce = lambda value: str(value).lower() - ); + return mk_config_property( field, doc=doc, + get_coerce = lambda value: value == "true", + set_coerce = lambda value: str(value).lower() + ) class MumbleServer( models.Model ): - """ Represents a Murmur server installation. """ - - dbus = models.CharField( _('DBus or ICE base'), max_length=200, unique=True, 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.") ); - secret = models.CharField( _('Ice Secret'), max_length=200, blank=True ); - - class Meta: - verbose_name = _('Mumble Server'); - verbose_name_plural = _('Mumble Servers'); - - def __init__( self, *args, **kwargs ): - models.Model.__init__( self, *args, **kwargs ); - self._ctl = None; - self._conf = None; - self._version = None; - - def __unicode__( self ): - return self.dbus; - - # 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, self.secret ); - return self._ctl; - - ctl = property( getCtl, doc="Get a Control object for this server. The ctl is cached for later reuse." ); - - def isMethodDbus(self): - """ Return true if this instance uses DBus. """ - rd = re.compile( r'^(\w+\.)*\w+$' ); - return bool(rd.match(self.dbus)) - - method_dbus = property( isMethodDbus ) - method_ice = property( lambda self: not self.isMethodDbus(), doc="Return true if this instance uses Ice." ) - - def getDefaultConf( self, field=None ): - """ Get a field from the default conf dictionary, or None if the field isn't set. """ - if self._conf is None: - self._conf = self.ctl.getDefaultConf() - if field is None: - return self._conf - if field in self._conf: - return self._conf[field] - return None - - def isOnline( self ): - """ Return true if this server process is running. """ - possibleexceptions = [] - try: - from Ice import ConnectionRefusedException - except ImportError, err: - if self.method_ice: - print >> stderr, err - return None - else: - possibleexceptions.append( ConnectionRefusedException ) - try: - from dbus import DBusException - except ImportError, err: - if self.method_dbus: - print >> stderr, err - return None - else: - possibleexceptions.append( DBusException ) - - try: - self.ctl - except tuple(possibleexceptions), err: - print >> stderr, err - return False - except (EnvironmentError, RuntimeError), err: - print >> stderr, err - return None - else: - return True - - online = property( isOnline ) - defaultconf = property( getDefaultConf, doc="The default config dictionary." ) - - def getDefaultPort( self ): - """ Return the default port configured on this server. """ - if "port" in self.defaultconf: - return int(self.defaultconf['port']) - else: - return settings.MUMBLE_DEFAULT_PORT - - defaultPort = property( getDefaultPort ) - - def getVersion( self ): - """ Return the version of Murmur. """ - if self._version is None: - self._version = self.ctl.getVersion() - return self._version - - version = property( getVersion ) - - prettyversion = property( lambda self: '.'.join( map( str, self.version[:3] ) ), - doc="Pretty-Printed version" ) + """ Represents a Murmur server installation. """ + + dbus = models.CharField( _('DBus or ICE base'), max_length=200, unique=True, 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.") ) + secret = models.CharField( _('Ice Secret'), max_length=200, blank=True ) + + class Meta: + verbose_name = _('Mumble Server') + verbose_name_plural = _('Mumble Servers') + + def __init__( self, *args, **kwargs ): + models.Model.__init__( self, *args, **kwargs ) + self._ctl = None + self._conf = None + self._version = None + + def __unicode__( self ): + return self.dbus + + # 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, self.secret ) + return self._ctl + + ctl = property( getCtl, doc="Get a Control object for this server. The ctl is cached for later reuse." ) + + def isMethodDbus(self): + """ Return true if this instance uses DBus. """ + rd = re.compile( r'^(\w+\.)*\w+$' ) + return bool(rd.match(self.dbus)) + + method_dbus = property( isMethodDbus ) + method_ice = property( lambda self: not self.isMethodDbus(), doc="Return true if this instance uses Ice." ) + + def getDefaultConf( self, field=None ): + """ Get a field from the default conf dictionary, or None if the field isn't set. """ + if self._conf is None: + self._conf = self.ctl.getDefaultConf() + if field is None: + return self._conf + if field in self._conf: + return self._conf[field] + return None + + def isOnline( self ): + """ Return true if this server process is running. """ + possibleexceptions = [] + try: + from Ice import ConnectionRefusedException + except ImportError, err: + if self.method_ice: + print >> stderr, err + return None + else: + possibleexceptions.append( ConnectionRefusedException ) + try: + from dbus import DBusException + except ImportError, err: + if self.method_dbus: + print >> stderr, err + return None + else: + possibleexceptions.append( DBusException ) + + try: + self.ctl + except tuple(possibleexceptions), err: + print >> stderr, err + return False + except (EnvironmentError, RuntimeError), err: + print >> stderr, err + return None + else: + return True + + online = property( isOnline ) + defaultconf = property( getDefaultConf, doc="The default config dictionary." ) + + def getDefaultPort( self ): + """ Return the default port configured on this server. """ + if "port" in self.defaultconf: + return int(self.defaultconf['port']) + else: + return settings.MUMBLE_DEFAULT_PORT + + defaultPort = property( getDefaultPort ) + + def getVersion( self ): + """ Return the version of Murmur. """ + if self._version is None: + self._version = self.ctl.getVersion() + return self._version + + version = property( getVersion ) + + prettyversion = property( lambda self: '.'.join( map( str, self.version[:3] ) ), + doc="Pretty-Printed version" ) 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. - """ - - server = models.ForeignKey( MumbleServer, verbose_name=_("Mumble Server") ); - name = models.CharField( _('Server Name'), max_length=200 ); - srvid = models.IntegerField( _('Server ID'), editable=False ); - addr = models.CharField( _('Server Address'), max_length=200, blank=True, 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'), blank=True, null=True, help_text=_( - "Port number to bind to. Leave empty to auto assign one.") ); - display = models.CharField( _('Server Display Address'), max_length=200, blank=True, help_text=_( - "This field is only relevant if you are located behind a NAT, and names the Hostname or IP address " - "to use in the Channel Viewer and for the global server list registration. If not given, the addr " - "and port fields are used. If display and bind ports are equal, you can omit it here.") ); - - supw = property( lambda self: '', - lambda self, value: ( value and self.ctl.setSuperUserPassword( self.srvid, value ) ) or None, - doc=_('Superuser Password') - ) - - url = mk_config_property( "registerurl", ugettext_noop("Website URL") ) - motd = mk_config_property( "welcometext", ugettext_noop("Welcome Message") ) - passwd = mk_config_property( "password", ugettext_noop("Server Password") ) - users = mk_config_property( "users", ugettext_noop("Max. Users"), get_coerce=int ) - bwidth = mk_config_property( "bandwidth", ugettext_noop("Bandwidth [Bps]"), get_coerce=int ) - sslcrt = mk_config_property( "certificate", ugettext_noop("SSL Certificate") ) - sslkey = mk_config_property( "key", ugettext_noop("SSL Key") ) - player = mk_config_property( "username", ugettext_noop("Player name regex") ) - channel = mk_config_property( "channelname", ugettext_noop("Channel name regex") ) - defchan = mk_config_property( "defaultchannel", ugettext_noop("Default channel"), get_coerce=int ) - timeout = mk_config_property( "timeout", ugettext_noop("Timeout"), get_coerce=int ) - #usersperchannel - - obfsc = mk_config_bool_property( "obfuscate", ugettext_noop("IP Obfuscation") ) - certreq = mk_config_bool_property( "certrequired", ugettext_noop("Require Certificate") ) - textlen = mk_config_bool_property( "textmessagelength", ugettext_noop("Maximum length of text messages") ) - html = mk_config_bool_property( "allowhtml", ugettext_noop("Allow HTML to be used in messages") ) - bonjour = mk_config_bool_property( "bonjour", ugettext_noop("Publish this server via Bonjour") ) - autoboot= mk_config_bool_property( "boot", ugettext_noop("Boot Server when Murmur starts") ) - - def getBooted( self ): - if self.id is not None: - if self.server.online: - return self.ctl.isBooted( self.srvid ) - else: - return None - 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=ugettext_noop("Boot Server") ) - online = property( getBooted, setBooted, doc=ugettext_noop("Boot Server") ) - - class Meta: - unique_together = ( ( 'server', 'srvid' ), ); - 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: - return models.Model.save( self ); - - if self.id is None: - self.srvid = self.ctl.newServer(); - - self.ctl.setConf( self.srvid, 'registername', self.name ); - - if self.addr: - if " " in self.addr: - # user gave multiple addresses, don't mess with that - self.ctl.setConf( self.srvid, 'host', self.addr ); - else: - self.ctl.setConf( self.srvid, 'host', get_ipv46_str_by_name(self.addr) ); - else: - self.ctl.setConf( self.srvid, 'host', '' ); - - if self.port and self.port != self.server.defaultPort + self.srvid - 1: - self.ctl.setConf( self.srvid, 'port', str(self.port) ); - else: - self.ctl.setConf( self.srvid, 'port', '' ); - - if self.netloc: - self.ctl.setConf( self.srvid, 'registerhostname', self.netloc ); - else: - self.ctl.setConf( self.srvid, 'registerhostname', '' ); - - return models.Model.save( self ); - - - def __init__( self, *args, **kwargs ): - models.Model.__init__( self, *args, **kwargs ); - 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: not self.passwd, - doc="False if a password is needed to join this server." ); - - is_server = True; - is_channel = False; - is_player = False; - - ctl = property( lambda self: self.server.ctl ); - - 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 ): - conf = self.ctl.getAllConf( self.srvid ); - - if "registername" not in conf or not conf["registername"]: - self.name = "noname"; - else: - self.name = conf["registername"]; - - if "registerhostname" in conf and conf["registerhostname"]: - if ':' in conf["registerhostname"]: - regname, regport = conf["registerhostname"].split(':') - regport = int(regport) - else: - regname = conf["registerhostname"] - regport = None - else: - regname = None - regport = None - - if "host" in conf and conf["host"]: - addr = conf["host"] - else: - addr = None - - if "port" in conf and conf["port"]: - self.port = int(conf["port"]) - else: - self.port = None - - if regname and addr: - if regport == self.port: - if get_ipv46_str_by_name(regname) == get_ipv46_str_by_name(addr): - self.display = '' - self.addr = regname - else: - self.display = regname - self.addr = addr - else: - self.display = conf["registerhostname"] - self.addr = addr - elif regname and not addr: - self.display = regname - self.addr = '' - elif addr and not regname: - self.display = '' - self.addr = addr - else: - self.display = '' - self.addr = '' - - 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); - known_ids = [rec["mumbleid"] - for rec in MumbleUser.objects.filter( server=self ).values( "mumbleid" ) - ] - - for idx in players: - playerdata = players[idx]; - - if playerdata.userid == 0: # Skip SuperUsers - continue; - if verbose > 1: - print "Checking Player with id %d." % playerdata.userid; - - if playerdata.userid not in known_ids: - 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 "Player '%s' is already known." % playerdata.name; - playerinstance = MumbleUser.objects.get( server=self, mumbleid=playerdata.userid ); - 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(): - if user.is_superuser: - return True; - 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 getNetloc( self ): - """ Return the address from the Display field (if any), or the server address. - Users from outside a NAT will need to use the Display address to connect - to this server instance. - """ - if self.display: - if ":" in self.display: - return self.display; - else: - daddr = self.display; - elif " " in self.addr: - daddr = self.addr.split(" ")[0]; - else: - daddr = self.addr; - - if self.port and self.port != settings.MUMBLE_DEFAULT_PORT: - return "%s:%d" % (daddr, self.port); - else: - return daddr; - - netloc = property( getNetloc ); - - def getURL( self, forUser = None ): - """ Create an URL of the form mumble://username@host:port/ for this server. """ - if not self.netloc: - return None - from urlparse import urlunsplit - versionstr = "version=%s" % self.prettyversion; - if forUser is not None: - netloc = "%s@%s" % ( forUser.name, self.netloc ); - return urlunsplit(( "mumble", netloc, "", versionstr, "" )) - else: - return urlunsplit(( "mumble", self.netloc, "", versionstr, "" )) - - connecturl = property( getURL ); - - version = property( lambda self: self.server.version, doc="The version of Murmur." ); - prettyversion = property( lambda self: self.server.prettyversion ); - - def asDict( self ): - return { 'name': self.name, - 'id': self.id, - 'root': self.rootchan.asDict() - }; - - def asMvXml( self ): - """ Return an XML tree for this server suitable for MumbleViewer-ng. """ - from xml.etree.cElementTree import Element - root = Element("root") - self.rootchan.asMvXml(root) - return root - - def asMvJson( self ): - """ Return a Dict for this server suitable for MumbleViewer-ng. """ - return self.rootchan.asMvJson() - - # "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 ); - - def kickUser( self, sessionid, reason="" ): - return self.ctl.kickUser( self.srvid, sessionid, reason ); - - def banUser( self, sessionid, reason="" ): - return self.ctl.addBanForSession( self.srvid, sessionid, reason=reason ); + """ 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. + """ + + server = models.ForeignKey( MumbleServer, verbose_name=_("Mumble Server") ) + name = models.CharField( _('Server Name'), max_length=200 ) + srvid = models.IntegerField( _('Server ID'), editable=False ) + addr = models.CharField( _('Server Address'), max_length=200, blank=True, 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'), blank=True, null=True, help_text=_( + "Port number to bind to. Leave empty to auto assign one.") ) + display = models.CharField( _('Server Display Address'), max_length=200, blank=True, help_text=_( + "This field is only relevant if you are located behind a NAT, and names the Hostname or IP address " + "to use in the Channel Viewer and for the global server list registration. If not given, the addr " + "and port fields are used. If display and bind ports are equal, you can omit it here.") ) + + supw = property( lambda self: '', + lambda self, value: ( value and self.ctl.setSuperUserPassword( self.srvid, value ) ) or None, + doc=_('Superuser Password') + ) + + url = mk_config_property( "registerurl", ugettext_noop("Website URL") ) + motd = mk_config_property( "welcometext", ugettext_noop("Welcome Message") ) + passwd = mk_config_property( "password", ugettext_noop("Server Password") ) + users = mk_config_property( "users", ugettext_noop("Max. Users"), get_coerce=int ) + bwidth = mk_config_property( "bandwidth", ugettext_noop("Bandwidth [Bps]"), get_coerce=int ) + sslcrt = mk_config_property( "certificate", ugettext_noop("SSL Certificate") ) + sslkey = mk_config_property( "key", ugettext_noop("SSL Key") ) + player = mk_config_property( "username", ugettext_noop("Player name regex") ) + channel = mk_config_property( "channelname", ugettext_noop("Channel name regex") ) + defchan = mk_config_property( "defaultchannel", ugettext_noop("Default channel"), get_coerce=int ) + timeout = mk_config_property( "timeout", ugettext_noop("Timeout"), get_coerce=int ) + #usersperchannel + + obfsc = mk_config_bool_property( "obfuscate", ugettext_noop("IP Obfuscation") ) + certreq = mk_config_bool_property( "certrequired", ugettext_noop("Require Certificate") ) + textlen = mk_config_bool_property( "textmessagelength", ugettext_noop("Maximum length of text messages") ) + html = mk_config_bool_property( "allowhtml", ugettext_noop("Allow HTML to be used in messages") ) + bonjour = mk_config_bool_property( "bonjour", ugettext_noop("Publish this server via Bonjour") ) + autoboot= mk_config_bool_property( "boot", ugettext_noop("Boot Server when Murmur starts") ) + + def getBooted( self ): + if self.id is not None: + if self.server.online: + return self.ctl.isBooted( self.srvid ) + else: + return None + 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=ugettext_noop("Boot Server") ) + online = property( getBooted, setBooted, doc=ugettext_noop("Boot Server") ) + + class Meta: + unique_together = ( ( 'server', 'srvid' ), ) + 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: + return models.Model.save( self ) + + if self.id is None: + self.srvid = self.ctl.newServer() + + self.ctl.setConf( self.srvid, 'registername', self.name ) + + if self.addr: + if " " in self.addr: + # user gave multiple addresses, don't mess with that + self.ctl.setConf( self.srvid, 'host', self.addr ) + else: + self.ctl.setConf( self.srvid, 'host', get_ipv46_str_by_name(self.addr) ) + else: + self.ctl.setConf( self.srvid, 'host', '' ) + + if self.port and self.port != self.server.defaultPort + self.srvid - 1: + self.ctl.setConf( self.srvid, 'port', str(self.port) ) + else: + self.ctl.setConf( self.srvid, 'port', '' ) + + if self.netloc: + self.ctl.setConf( self.srvid, 'registerhostname', self.netloc ) + else: + self.ctl.setConf( self.srvid, 'registerhostname', '' ) + + return models.Model.save( self ) + + + def __init__( self, *args, **kwargs ): + models.Model.__init__( self, *args, **kwargs ) + 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: not self.passwd, + doc="False if a password is needed to join this server." ) + + is_server = True + is_channel = False + is_player = False + + ctl = property( lambda self: self.server.ctl ) + + 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 ): + conf = self.ctl.getAllConf( self.srvid ) + + if "registername" not in conf or not conf["registername"]: + self.name = "noname" + else: + self.name = conf["registername"] + + if "registerhostname" in conf and conf["registerhostname"]: + if ':' in conf["registerhostname"]: + regname, regport = conf["registerhostname"].split(':') + regport = int(regport) + else: + regname = conf["registerhostname"] + regport = None + else: + regname = None + regport = None + + if "host" in conf and conf["host"]: + addr = conf["host"] + else: + addr = None + + if "port" in conf and conf["port"]: + self.port = int(conf["port"]) + else: + self.port = None + + if regname and addr: + if regport == self.port: + if get_ipv46_str_by_name(regname) == get_ipv46_str_by_name(addr): + self.display = '' + self.addr = regname + else: + self.display = regname + self.addr = addr + else: + self.display = conf["registerhostname"] + self.addr = addr + elif regname and not addr: + self.display = regname + self.addr = '' + elif addr and not regname: + self.display = '' + self.addr = addr + else: + self.display = '' + self.addr = '' + + 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) + known_ids = [rec["mumbleid"] + for rec in MumbleUser.objects.filter( server=self ).values( "mumbleid" ) + ] + + for idx in players: + playerdata = players[idx] + + if playerdata.userid == 0: # Skip SuperUsers + continue + if verbose > 1: + print "Checking Player with id %d." % playerdata.userid + + if playerdata.userid not in known_ids: + 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 "Player '%s' is already known." % playerdata.name + playerinstance = MumbleUser.objects.get( server=self, mumbleid=playerdata.userid ) + 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(): + if user.is_superuser: + return True + 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 getNetloc( self ): + """ Return the address from the Display field (if any), or the server address. + Users from outside a NAT will need to use the Display address to connect + to this server instance. + """ + if self.display: + if ":" in self.display: + return self.display + else: + daddr = self.display + elif " " in self.addr: + daddr = self.addr.split(" ")[0] + else: + daddr = self.addr + + if self.port and self.port != settings.MUMBLE_DEFAULT_PORT: + return "%s:%d" % (daddr, self.port) + else: + return daddr + + netloc = property( getNetloc ) + + def getURL( self, forUser = None ): + """ Create an URL of the form mumble://username@host:port/ for this server. """ + if not self.netloc: + return None + from urlparse import urlunsplit + versionstr = "version=%s" % self.prettyversion + if forUser is not None: + netloc = "%s@%s" % ( forUser.name, self.netloc ) + return urlunsplit(( "mumble", netloc, "", versionstr, "" )) + else: + return urlunsplit(( "mumble", self.netloc, "", versionstr, "" )) + + connecturl = property( getURL ) + + version = property( lambda self: self.server.version, doc="The version of Murmur." ) + prettyversion = property( lambda self: self.server.prettyversion ) + + def asDict( self ): + return { 'name': self.name, + 'id': self.id, + 'root': self.rootchan.asDict() + } + + def asMvXml( self ): + """ Return an XML tree for this server suitable for MumbleViewer-ng. """ + from xml.etree.cElementTree import Element + root = Element("root") + self.rootchan.asMvXml(root) + return root + + def asMvJson( self ): + """ Return a Dict for this server suitable for MumbleViewer-ng. """ + return self.rootchan.asMvJson() + + # "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 ) + + def kickUser( self, sessionid, reason="" ): + return self.ctl.kickUser( self.srvid, sessionid, reason ) + + def banUser( self, sessionid, reason="" ): + return self.ctl.addBanForSession( self.srvid, sessionid, reason=reason ) def mk_registration_property( field, doc="" ): - """ Create a property for the given registration field. """ - - def get_field( self ): - if field in self.registration: - return self.registration[field]; - else: - return None; - - return property( get_field, doc=doc ) + """ Create a property for the given registration field. """ + + def get_field( self ): + if field in self.registration: + return self.registration[field] + else: + return None + + return property( get_field, doc=doc ) 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 ); - - comment = mk_registration_property( "comment", doc=ugettext_noop("The user's comment.") ); - hash = mk_registration_property( "hash", doc=ugettext_noop("The user's hash.") ); - - gravatar256 = property( lambda self: self.gravatarUrl( size=256 ) ) - gravatar128 = property( lambda self: self.gravatarUrl( size=128 ) ) - gravatar64 = property( lambda self: self.gravatarUrl( size=64 ) ) - gravatar = property( lambda self: self.gravatarUrl() ) - - 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: - return models.Model.save( self ); - - 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 = ''; - - # If enabled (and possible), set Gravatar as default Avatar - if settings.USE_GRAVATAR and self.gravatar: - self.setTextureFromUrl( self.gravatar ) - - 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. """ - if self.mumbleid == -1: - return False; - else: - 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 self.mumbleid == -1: - return False; - 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=ugettext_noop('Admin on root channel') ); - - - # 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 ); - - # 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, image ): - """ Install the given image as the user's texture. """ - self.server.ctl.setTexture(self.server.srvid, self.mumbleid, image) - - def setTextureFromUrl( self, url, transparency=None ): - """ Retrieve the image at the given URL and set it as my texture. """ - img = Image.open( StringIO( urlopen( url ).read() ) ) - self.setTexture( img ) - - texture = property( getTexture, setTexture, - doc="Get the texture as a PIL Image or set the Image as the texture." - ); - - def hasTexture( self ): - """ Check if this user has a texture set. """ - try: - self.getTexture(); - except ValueError: - return False; - else: - return True; - - def gravatarUrl( self, size=80 ): - """ Get a Gravatar URL for my owner's email adress (if any), or using the User's cert hash. - The size parameter is optional, and defaults to 80 pixels (the default used by Gravatar - if you omit this parameter in the URL). - """ - if self.owner and self.owner.email: - from hashlib import md5 - return settings.GRAVATAR_URL % { 'hash': md5(self.owner.email).hexdigest(), 'size': size } - elif self.hash: - return settings.GRAVATAR_URL % { 'hash': self.hash, 'size': size } - return None - - 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 ); - - - # 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 ); + """ 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 ) + + comment = mk_registration_property( "comment", doc=ugettext_noop("The user's comment.") ) + hash = mk_registration_property( "hash", doc=ugettext_noop("The user's hash.") ) + + gravatar256 = property( lambda self: self.gravatarUrl( size=256 ) ) + gravatar128 = property( lambda self: self.gravatarUrl( size=128 ) ) + gravatar64 = property( lambda self: self.gravatarUrl( size=64 ) ) + gravatar = property( lambda self: self.gravatarUrl() ) + + 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: + return models.Model.save( self ) + + 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 = '' + + # If enabled (and possible), set Gravatar as default Avatar + if settings.USE_GRAVATAR and self.gravatar: + self.setTextureFromUrl( self.gravatar ) + + 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. """ + if self.mumbleid == -1: + return False + else: + 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 self.mumbleid == -1: + return False + 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=ugettext_noop('Admin on root channel') ) + + + # 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 ) + + # 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, image ): + """ Install the given image as the user's texture. """ + self.server.ctl.setTexture(self.server.srvid, self.mumbleid, image) + + def setTextureFromUrl( self, url, transparency=None ): + """ Retrieve the image at the given URL and set it as my texture. """ + img = Image.open( StringIO( urlopen( url ).read() ) ) + self.setTexture( img ) + + texture = property( getTexture, setTexture, + doc="Get the texture as a PIL Image or set the Image as the texture." + ) + + def hasTexture( self ): + """ Check if this user has a texture set. """ + try: + self.getTexture() + except ValueError: + return False + else: + return True + + def gravatarUrl( self, size=80 ): + """ Get a Gravatar URL for my owner's email adress (if any), or using the User's cert hash. + The size parameter is optional, and defaults to 80 pixels (the default used by Gravatar + if you omit this parameter in the URL). + """ + if self.owner and self.owner.email: + from hashlib import md5 + return settings.GRAVATAR_URL % { 'hash': md5(self.owner.email).hexdigest(), 'size': size } + elif self.hash: + return settings.GRAVATAR_URL % { 'hash': self.hash, 'size': size } + return None + + 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 ) + + + # 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 ) diff --git a/pyweb/mumble/murmurenvutils.py b/pyweb/mumble/murmurenvutils.py index 8231e79..07deb43 100644 --- a/pyweb/mumble/murmurenvutils.py +++ b/pyweb/mumble/murmurenvutils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -15,230 +16,230 @@ """ import os, subprocess, signal -from select import select -from os.path import join, exists -from shutil import copyfile +from select import select +from os.path import join, exists +from shutil import copyfile -from django.conf import settings +from django.conf import settings -from utils import ObjectInfo +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; + """ 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: - - ( intended_return_value, 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 ); + """ 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: + + ( intended_return_value, 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 ); + """ 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 ); + """ 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) ); + """ 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; + """ 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 ); + """ 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 ); + """ 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 ); + """ 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 ) diff --git a/pyweb/mumble/templatetags/mumble_extras.py b/pyweb/mumble/templatetags/mumble_extras.py index 2398aba..8639458 100644 --- a/pyweb/mumble/templatetags/mumble_extras.py +++ b/pyweb/mumble/templatetags/mumble_extras.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -19,44 +20,44 @@ from django.template.loader import render_to_string from django.conf import settings -register = template.Library(); +register = template.Library() @register.filter def trunc( string, maxlen = 50 ): - """ converts "a very very extaordinary long text" to "a very very extra... """ - if len(string) < maxlen: - return string; - return string[:(maxlen - 3)] + "…"; + """ converts "a very very extaordinary long text" to "a very very extra... """ + if len(string) < maxlen: + return string + return string[:(maxlen - 3)] + "…" @register.filter def chanview( obj, user = None ): - """ renders an mmChannel / mmPlayer object with the correct template """ - if obj.is_server: - return render_to_string( 'mumble/server.html', { 'Server': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } ); - elif obj.is_channel: - return render_to_string( 'mumble/channel.html', { 'Channel': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } ); - elif obj.is_player: - return render_to_string( 'mumble/player.html', { 'Player': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } ); + """ renders an mmChannel / mmPlayer object with the correct template """ + if obj.is_server: + return render_to_string( 'mumble/server.html', { 'Server': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } ) + elif obj.is_channel: + return render_to_string( 'mumble/channel.html', { 'Channel': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } ) + elif obj.is_player: + return render_to_string( 'mumble/player.html', { 'Player': obj, 'MumbleAccount': user, 'MEDIA_URL': settings.MEDIA_URL } ) @register.filter def chanurl( obj, user ): - """ create a connection URL and takes the user's login into account """ - return obj.getURL( user ); + """ create a connection URL and takes the user's login into account """ + return obj.getURL( user ) @register.filter def mmversion_lt( obj, version ): - """ return True if the given Server's version is less than the given version. """ - return tuple(obj.version[:3]) < tuple([int(v) for v in version.split('.')]) + """ return True if the given Server's version is less than the given version. """ + return tuple(obj.version[:3]) < tuple([int(v) for v in version.split('.')]) @register.filter def mmversion_eq( obj, version ): - """ return True if the given Server's version equals the given version. """ - return tuple(obj.version[:3]) == tuple([int(v) for v in version.split('.')]) + """ return True if the given Server's version equals the given version. """ + return tuple(obj.version[:3]) == tuple([int(v) for v in version.split('.')]) @register.filter def mmversion_gt( obj, version ): - """ return True if the given Server's version is greater than the given version. """ - return tuple(obj.version[:3]) > tuple([int(v) for v in version.split('.')]) + """ return True if the given Server's version is greater than the given version. """ + return tuple(obj.version[:3]) > tuple([int(v) for v in version.split('.')]) diff --git a/pyweb/mumble/testrunner.py b/pyweb/mumble/testrunner.py index e1e30c1..184f2c2 100644 --- a/pyweb/mumble/testrunner.py +++ b/pyweb/mumble/testrunner.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -16,84 +17,84 @@ import os -from django.test.simple import run_tests as django_run_tests -from django.conf import settings +from django.test.simple import run_tests as django_run_tests +from django.conf import settings -from murmurenvutils import get_available_versions, run_callback, wait_for_user +from murmurenvutils import get_available_versions, run_callback, wait_for_user def run_tests( test_labels, verbosity=1, interactive=True, extra_tests=[] ): - """ Run the Django built in testing framework, but before testing the mumble - app, allow Murmur to be set up correctly. - """ - - if not test_labels: - test_labels = [ appname.split('.')[-1] for appname in settings.INSTALLED_APPS ]; - - # No need to sync any murmur servers for the other apps - os.environ['MURMUR_CONNSTR'] = ''; - - # The easy way: mumble is not being tested. - if "mumble" not in test_labels: - return django_run_tests( test_labels, verbosity, interactive, extra_tests ); - - # First run everything apart from mumble. mumble will be tested separately, so Murmur - # can be set up properly first. - failed_tests = 0; - - if len(test_labels) > 1: - # only run others if mumble is not the only app to be tested - test_labels = list(test_labels); - test_labels.remove( "mumble" ); - failed_tests += django_run_tests( test_labels, verbosity, interactive, extra_tests ); - - failed_tests += run_mumble_tests( verbosity, interactive ); - - return failed_tests; + """ Run the Django built in testing framework, but before testing the mumble + app, allow Murmur to be set up correctly. + """ + + if not test_labels: + test_labels = [ appname.split('.')[-1] for appname in settings.INSTALLED_APPS ] + + # No need to sync any murmur servers for the other apps + os.environ['MURMUR_CONNSTR'] = '' + + # The easy way: mumble is not being tested. + if "mumble" not in test_labels: + return django_run_tests( test_labels, verbosity, interactive, extra_tests ) + + # First run everything apart from mumble. mumble will be tested separately, so Murmur + # can be set up properly first. + failed_tests = 0 + + if len(test_labels) > 1: + # only run others if mumble is not the only app to be tested + test_labels = list(test_labels) + test_labels.remove( "mumble" ) + failed_tests += django_run_tests( test_labels, verbosity, interactive, extra_tests ) + + failed_tests += run_mumble_tests( verbosity, interactive ) + + return failed_tests def run_mumble_tests( verbosity=1, interactive=True ): - - connstrings = { - 'DBus': 'net.sourceforge.mumble.murmur', - 'Ice': 'Meta:tcp -h 127.0.0.1 -p 6502', - }; - - def django_run_tests_wrapper( process, version ): - wr_failed_tests = 0; - - for method in connstrings: - # Check if this server is ready to be used with the current method - if getattr( process.capabilities, ("has_%s" % method.lower()), False ): - print "Testing mumble %s via %s" % ( version, method ); - - os.environ['MURMUR_CONNSTR'] = connstrings[method]; - settings.DEFAULT_CONN = connstrings[method]; - settings.SLICE_VERSION = [ int(dgt) for dgt in version.split('.') ]; - - print "MURMUR_CONNSTR:", os.environ['MURMUR_CONNSTR']; - print "DEFAULT_CONN: ", settings.DEFAULT_CONN; - print "SLICE_VERSION: ", settings.SLICE_VERSION; - - if not process.capabilities.has_users: - print "Waiting for user to connect (60 seconds)." - wait_for_user( process, timeout=60 ); - - wr_failed_tests += django_run_tests( ('mumble',), verbosity, interactive, [] ); - else: - print "Mumble %s does not support Method %s" % ( version, method ); - - return wr_failed_tests; - - failed_tests = 0; - - from mctl import MumbleCtlBase - - for version in get_available_versions(): - MumbleCtlBase.clearCache(); - - run = raw_input( "Run tests for %s? [Y/n] " % version ); - if run in ('Y', 'y', ''): - failed_tests += run_callback( version, django_run_tests_wrapper, version ); - - return failed_tests; + + connstrings = { + 'DBus': 'net.sourceforge.mumble.murmur', + 'Ice': 'Meta:tcp -h 127.0.0.1 -p 6502', + } + + def django_run_tests_wrapper( process, version ): + wr_failed_tests = 0 + + for method in connstrings: + # Check if this server is ready to be used with the current method + if getattr( process.capabilities, ("has_%s" % method.lower()), False ): + print "Testing mumble %s via %s" % ( version, method ) + + os.environ['MURMUR_CONNSTR'] = connstrings[method] + settings.DEFAULT_CONN = connstrings[method] + settings.SLICE_VERSION = [ int(dgt) for dgt in version.split('.') ] + + print "MURMUR_CONNSTR:", os.environ['MURMUR_CONNSTR'] + print "DEFAULT_CONN: ", settings.DEFAULT_CONN + print "SLICE_VERSION: ", settings.SLICE_VERSION + + if not process.capabilities.has_users: + print "Waiting for user to connect (60 seconds)." + wait_for_user( process, timeout=60 ) + + wr_failed_tests += django_run_tests( ('mumble',), verbosity, interactive, [] ) + else: + print "Mumble %s does not support Method %s" % ( version, method ) + + return wr_failed_tests + + failed_tests = 0 + + from mctl import MumbleCtlBase + + for version in get_available_versions(): + MumbleCtlBase.clearCache() + + run = raw_input( "Run tests for %s? [Y/n] " % version ) + if run in ('Y', 'y', ''): + failed_tests += run_callback( version, django_run_tests_wrapper, version ) + + return failed_tests diff --git a/pyweb/mumble/tests.py b/pyweb/mumble/tests.py index eaedc84..aab57e5 100644 --- a/pyweb/mumble/tests.py +++ b/pyweb/mumble/tests.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -14,195 +15,195 @@ * GNU General Public License for more details. """ -from django.conf import settings -from django.test import TestCase +from django.conf import settings +from django.test import TestCase -from models import Mumble -from utils import ObjectInfo +from models import Mumble +from utils import ObjectInfo class InstancesHandling( TestCase ): - """ Tests creation, editing and removing of vserver instances. """ - - def setUp( self ): - # Make sure we always start with a FRESH murmur instance, checking for left-over instances - # and deleting them before creating ours. - try: - self.murmur = Mumble.objects.get( addr="0.0.0.0", port=31337 ); - except Mumble.DoesNotExist: - pass - else: - self.murmur.delete(); - finally: - self.murmur = Mumble( name="#unit testing instance#", addr="0.0.0.0", port=31337 ); - self.murmur.save(); - - def testDefaultConf( self ): - conf = self.murmur.ctl.getAllConf( self.murmur.srvid ); - - self.assert_( type(conf) == dict ); - self.assert_( "host" in conf ); - self.assert_( "port" in conf ); - self.assert_( "certificate" in conf ); - self.assert_( "key" in conf ); - self.assert_( "registerhostname" in conf ); - self.assert_( "registername" in conf ); - self.assert_( "channelname" in conf ); - self.assert_( "username" in conf ); - self.assert_( "obfuscate" in conf ); - self.assert_( "defaultchannel" in conf ); - - def testAddrPortUnique( self ): - try: - duplicate = Mumble( - name="#another unit testing instance#", - addr=self.murmur.addr, port=self.murmur.port, - dbus=settings.DEFAULT_CONN - ); - if duplicate.ctl.method == "ICE": - import Murmur - self.assertRaises( Murmur.ServerFailureException, duplicate.save ); - elif self.murmur.version[:2] == [ 1, 2 ]: - from dbus import DBusException - self.assertRaises( DBusException, duplicate.save ); - else: - from sqlite3 import IntegrityError - self.assertRaises( IntegrityError, duplicate.save ); - finally: - # make sure the duplicate is removed - duplicate.ctl.deleteServer( duplicate.srvid ); - - def tearDown( self ): - self.murmur.delete(); + """ Tests creation, editing and removing of vserver instances. """ + + def setUp( self ): + # Make sure we always start with a FRESH murmur instance, checking for left-over instances + # and deleting them before creating ours. + try: + self.murmur = Mumble.objects.get( addr="0.0.0.0", port=31337 ) + except Mumble.DoesNotExist: + pass + else: + self.murmur.delete() + finally: + self.murmur = Mumble( name="#unit testing instance#", addr="0.0.0.0", port=31337 ) + self.murmur.save() + + def testDefaultConf( self ): + conf = self.murmur.ctl.getAllConf( self.murmur.srvid ) + + self.assert_( type(conf) == dict ) + self.assert_( "host" in conf ) + self.assert_( "port" in conf ) + self.assert_( "certificate" in conf ) + self.assert_( "key" in conf ) + self.assert_( "registerhostname" in conf ) + self.assert_( "registername" in conf ) + self.assert_( "channelname" in conf ) + self.assert_( "username" in conf ) + self.assert_( "obfuscate" in conf ) + self.assert_( "defaultchannel" in conf ) + + def testAddrPortUnique( self ): + try: + duplicate = Mumble( + name="#another unit testing instance#", + addr=self.murmur.addr, port=self.murmur.port, + dbus=settings.DEFAULT_CONN + ) + if duplicate.ctl.method == "ICE": + import Murmur + self.assertRaises( Murmur.ServerFailureException, duplicate.save ) + elif self.murmur.version[:2] == [ 1, 2 ]: + from dbus import DBusException + self.assertRaises( DBusException, duplicate.save ) + else: + from sqlite3 import IntegrityError + self.assertRaises( IntegrityError, duplicate.save ) + finally: + # make sure the duplicate is removed + duplicate.ctl.deleteServer( duplicate.srvid ) + + def tearDown( self ): + self.murmur.delete() class DataReading( TestCase ): - """ Tests reading data from murmur using the low-level CTL methods. """ - - def setUp( self ): - # BIG FAT WARNING: This sucks ass, because it assumes the tester has a - # Murmur database like the one I have. - # I definitely need to prepare Murmur somehow before running these tests. - # Just don't yet know how. - self.murmur = Mumble.objects.get(id=1); - - - def testCtlGetChannels( self ): - """ Test getChannels() """ - - channels = self.murmur.ctl.getChannels( self.murmur.srvid ); - - if self.murmur.ctl.method == "ICE": - import Murmur - self.assertEquals( type( channels[0] ), Murmur.Channel ); - else: - self.assertEquals( type( channels[0] ), ObjectInfo ); - - self.assert_( hasattr( channels[0], "id" ) ); - self.assert_( hasattr( channels[0], "name" ) ); - self.assert_( hasattr( channels[0], "parent" ) ); - self.assert_( hasattr( channels[0], "links" ) ); - - - def testCtlGetPlayers( self ): - """ Test getPlayers() """ - - players = self.murmur.ctl.getPlayers( self.murmur.srvid ); - - self.assert_( len(players) > 0 ); - - self.assertEquals( type(players), dict ); - - for plidx in players: - player = players[plidx]; - - if self.murmur.ctl.method == "ICE" and self.murmur.version[:2] == ( 1, 2 ): - import Murmur - self.assertEquals( type( player ), Murmur.User ); - else: - self.assertEquals( type( player ), ObjectInfo ); - - self.assert_( hasattr( player, "session" ) ); - self.assert_( hasattr( player, "mute" ) ); - self.assert_( hasattr( player, "deaf" ) ); - self.assert_( hasattr( player, "selfMute" ) ); - self.assert_( hasattr( player, "selfDeaf" ) ); - self.assert_( hasattr( player, "channel" ) ); - self.assert_( hasattr( player, "userid" ) ); - self.assert_( hasattr( player, "name" ) ); - self.assert_( hasattr( player, "onlinesecs" ) ); - self.assert_( hasattr( player, "bytespersec" ) ); - - - def testCtlGetRegisteredPlayers( self ): - """ Test getRegistredPlayers() and getRegistration() """ - - players = self.murmur.ctl.getRegisteredPlayers( self.murmur.srvid ); - - self.assert_( len(players) > 0 ); - - self.assertEquals( type(players), dict ); - - for plidx in players: - player = players[plidx]; - - self.assertEquals( type( player ), ObjectInfo ); - - self.assert_( hasattr( player, "userid" ) ); - self.assert_( hasattr( player, "name" ) ); - self.assert_( hasattr( player, "email" ) ); - self.assert_( hasattr( player, "pw" ) ); - - # compare with getRegistration result - reg = self.murmur.ctl.getRegistration( self.murmur.srvid, player.userid ); - - self.assertEquals( type( reg ), ObjectInfo ); - - self.assert_( hasattr( reg, "userid" ) ); - self.assert_( hasattr( reg, "name" ) ); - self.assert_( hasattr( reg, "email" ) ); - self.assert_( hasattr( reg, "pw" ) ); - - self.assertEquals( player.userid, reg.userid ); - self.assertEquals( player.name, reg.name ); - self.assertEquals( player.email, reg.email ); - self.assertEquals( player.pw, reg.pw ); - - - def testCtlGetAcl( self ): - """ Test getACL() for the root channel """ - - acls, groups, inherit = self.murmur.ctl.getACL( self.murmur.srvid, 0 ); - - for rule in acls: - if self.murmur.ctl.method == "ICE" and self.murmur.version[:2] == ( 1, 2 ): - import Murmur - self.assertEquals( type( rule ), Murmur.ACL ); - else: - self.assertEquals( type( rule ), ObjectInfo ); - - self.assert_( hasattr( rule, "applyHere" ) ); - self.assert_( hasattr( rule, "applySubs" ) ); - self.assert_( hasattr( rule, "inherited" ) ); - self.assert_( hasattr( rule, "userid" ) ); - self.assert_( hasattr( rule, "group" ) ); - self.assert_( hasattr( rule, "allow" ) ); - self.assert_( hasattr( rule, "deny" ) ); - - for grp in groups: - if self.murmur.ctl.method == "ICE": - import Murmur - self.assertEquals( type( grp ), Murmur.Group ); - else: - self.assertEquals( type( grp ), ObjectInfo ); - - self.assert_( hasattr( grp, "name" ) ); - self.assert_( hasattr( grp, "inherited" ) ); - self.assert_( hasattr( grp, "inherit" ) ); - self.assert_( hasattr( grp, "inheritable" ) ); - self.assert_( hasattr( grp, "add" ) ); - self.assert_( hasattr( grp, "remove" ) ); - self.assert_( hasattr( grp, "members" ) ); + """ Tests reading data from murmur using the low-level CTL methods. """ + + def setUp( self ): + # BIG FAT WARNING: This sucks ass, because it assumes the tester has a + # Murmur database like the one I have. + # I definitely need to prepare Murmur somehow before running these tests. + # Just don't yet know how. + self.murmur = Mumble.objects.get(id=1) + + + def testCtlGetChannels( self ): + """ Test getChannels() """ + + channels = self.murmur.ctl.getChannels( self.murmur.srvid ) + + if self.murmur.ctl.method == "ICE": + import Murmur + self.assertEquals( type( channels[0] ), Murmur.Channel ) + else: + self.assertEquals( type( channels[0] ), ObjectInfo ) + + self.assert_( hasattr( channels[0], "id" ) ) + self.assert_( hasattr( channels[0], "name" ) ) + self.assert_( hasattr( channels[0], "parent" ) ) + self.assert_( hasattr( channels[0], "links" ) ) + + + def testCtlGetPlayers( self ): + """ Test getPlayers() """ + + players = self.murmur.ctl.getPlayers( self.murmur.srvid ) + + self.assert_( len(players) > 0 ) + + self.assertEquals( type(players), dict ) + + for plidx in players: + player = players[plidx] + + if self.murmur.ctl.method == "ICE" and self.murmur.version[:2] == ( 1, 2 ): + import Murmur + self.assertEquals( type( player ), Murmur.User ) + else: + self.assertEquals( type( player ), ObjectInfo ) + + self.assert_( hasattr( player, "session" ) ) + self.assert_( hasattr( player, "mute" ) ) + self.assert_( hasattr( player, "deaf" ) ) + self.assert_( hasattr( player, "selfMute" ) ) + self.assert_( hasattr( player, "selfDeaf" ) ) + self.assert_( hasattr( player, "channel" ) ) + self.assert_( hasattr( player, "userid" ) ) + self.assert_( hasattr( player, "name" ) ) + self.assert_( hasattr( player, "onlinesecs" ) ) + self.assert_( hasattr( player, "bytespersec" ) ) + + + def testCtlGetRegisteredPlayers( self ): + """ Test getRegistredPlayers() and getRegistration() """ + + players = self.murmur.ctl.getRegisteredPlayers( self.murmur.srvid ) + + self.assert_( len(players) > 0 ) + + self.assertEquals( type(players), dict ) + + for plidx in players: + player = players[plidx] + + self.assertEquals( type( player ), ObjectInfo ) + + self.assert_( hasattr( player, "userid" ) ) + self.assert_( hasattr( player, "name" ) ) + self.assert_( hasattr( player, "email" ) ) + self.assert_( hasattr( player, "pw" ) ) + + # compare with getRegistration result + reg = self.murmur.ctl.getRegistration( self.murmur.srvid, player.userid ) + + self.assertEquals( type( reg ), ObjectInfo ) + + self.assert_( hasattr( reg, "userid" ) ) + self.assert_( hasattr( reg, "name" ) ) + self.assert_( hasattr( reg, "email" ) ) + self.assert_( hasattr( reg, "pw" ) ) + + self.assertEquals( player.userid, reg.userid ) + self.assertEquals( player.name, reg.name ) + self.assertEquals( player.email, reg.email ) + self.assertEquals( player.pw, reg.pw ) + + + def testCtlGetAcl( self ): + """ Test getACL() for the root channel """ + + acls, groups, inherit = self.murmur.ctl.getACL( self.murmur.srvid, 0 ) + + for rule in acls: + if self.murmur.ctl.method == "ICE" and self.murmur.version[:2] == ( 1, 2 ): + import Murmur + self.assertEquals( type( rule ), Murmur.ACL ) + else: + self.assertEquals( type( rule ), ObjectInfo ) + + self.assert_( hasattr( rule, "applyHere" ) ) + self.assert_( hasattr( rule, "applySubs" ) ) + self.assert_( hasattr( rule, "inherited" ) ) + self.assert_( hasattr( rule, "userid" ) ) + self.assert_( hasattr( rule, "group" ) ) + self.assert_( hasattr( rule, "allow" ) ) + self.assert_( hasattr( rule, "deny" ) ) + + for grp in groups: + if self.murmur.ctl.method == "ICE": + import Murmur + self.assertEquals( type( grp ), Murmur.Group ) + else: + self.assertEquals( type( grp ), ObjectInfo ) + + self.assert_( hasattr( grp, "name" ) ) + self.assert_( hasattr( grp, "inherited" ) ) + self.assert_( hasattr( grp, "inherit" ) ) + self.assert_( hasattr( grp, "inheritable" ) ) + self.assert_( hasattr( grp, "add" ) ) + self.assert_( hasattr( grp, "remove" ) ) + self.assert_( hasattr( grp, "members" ) ) diff --git a/pyweb/mumble/urls.py b/pyweb/mumble/urls.py index 776e5bb..713d51e 100644 --- a/pyweb/mumble/urls.py +++ b/pyweb/mumble/urls.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -17,19 +18,19 @@ from django.conf.urls.defaults import patterns urlpatterns = patterns( - 'mumble.views', - ( r'djangousers', 'djangousers' ), - ( r'(?P\d+)/users', 'users' ), - - ( r'(?P\d+)/(?P\d+)/texture.png', 'showTexture' ), - - ( r'murmur/tree/(?P\d+)', 'mmng_tree' ), - ( r'mumbleviewer/(?P\d+).xml', 'mumbleviewer_tree_xml' ), - ( r'mumbleviewer/(?P\d+).json', 'mumbleviewer_tree_json'), - - ( r'mobile/(?P\d+)', 'mobile_show' ), - ( r'mobile/?$', 'mobile_mumbles' ), - - ( r'(?P\d+)', 'show' ), - ( r'$', 'mumbles' ), + 'mumble.views', + ( r'djangousers', 'djangousers' ), + ( r'(?P\d+)/users', 'users' ), + + ( r'(?P\d+)/(?P\d+)/texture.png', 'showTexture' ), + + ( r'murmur/tree/(?P\d+)', 'mmng_tree' ), + ( r'mumbleviewer/(?P\d+).xml', 'mumbleviewer_tree_xml' ), + ( r'mumbleviewer/(?P\d+).json', 'mumbleviewer_tree_json'), + + ( r'mobile/(?P\d+)', 'mobile_show' ), + ( r'mobile/?$', 'mobile_mumbles' ), + + ( r'(?P\d+)', 'show' ), + ( r'$', 'mumbles' ), ) diff --git a/pyweb/mumble/utils.py b/pyweb/mumble/utils.py index 4f2b36b..52a6732 100644 --- a/pyweb/mumble/utils.py +++ b/pyweb/mumble/utils.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -15,22 +16,22 @@ """ 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__ ); - - def __contains__( self, name ): - return name in self.__dict__; - - def __getitem__( self, name ): - return self.__dict__[name]; + """ 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__ ) + + def __contains__( self, name ): + return name in self.__dict__ + + def __getitem__( self, name ): + return self.__dict__[name] diff --git a/pyweb/mumble/views.py b/pyweb/mumble/views.py index dfaa2d5..e24048a 100644 --- a/pyweb/mumble/views.py +++ b/pyweb/mumble/views.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -15,398 +16,387 @@ """ import simplejson -from StringIO import StringIO -from PIL import Image +from StringIO import StringIO +from PIL import Image -from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404 -from django.template import RequestContext -from django.http import Http404, HttpResponse, HttpResponseRedirect -from django.contrib.auth.decorators import login_required -from django.contrib.auth.models import User -from django.conf import settings -from django.core.urlresolvers import reverse +from django.shortcuts import render_to_response, get_object_or_404, get_list_or_404 +from django.template import RequestContext +from django.http import Http404, HttpResponse, HttpResponseRedirect +from django.conf import settings +from django.contrib.auth.decorators import login_required +from django.contrib.auth.models import User +from django.contrib.auth import views as auth_views +from django.core.urlresolvers import reverse -from django.contrib.auth import views as auth_views - -from models import Mumble, MumbleUser -from forms import MumbleForm, MumbleUserForm, MumbleUserPasswordForm -from forms import MumbleUserLinkForm, MumbleTextureForm, MumbleKickForm +from models import Mumble, MumbleUser +from forms import MumbleForm, MumbleUserForm, MumbleUserPasswordForm +from forms import MumbleUserLinkForm, MumbleTextureForm, MumbleKickForm def redir( request ): - """ Redirect to the servers list. """ - if request.META['HTTP_USER_AGENT'].startswith( 'BlackBerry' ) or \ - "Opera Mobi" in request.META['HTTP_USER_AGENT'] or \ - "Opera Mini" in request.META['HTTP_USER_AGENT'] or \ - "Windows CE" in request.META['HTTP_USER_AGENT'] or \ - "MIDP" in request.META['HTTP_USER_AGENT'] or \ - "Palm" in request.META['HTTP_USER_AGENT'] or \ - "NetFront" in request.META['HTTP_USER_AGENT'] or \ - "Nokia" in request.META['HTTP_USER_AGENT'] or \ - "Symbian" in request.META['HTTP_USER_AGENT'] or \ - "UP.Browser" in request.META['HTTP_USER_AGENT'] or \ - "UP.Link" in request.META['HTTP_USER_AGENT'] or \ - "WinWAP" in request.META['HTTP_USER_AGENT'] or \ - "Android" in request.META['HTTP_USER_AGENT'] or \ - "DoCoMo" in request.META['HTTP_USER_AGENT'] or \ - "KDDI-" in request.META['HTTP_USER_AGENT'] or \ - "Softbank" in request.META['HTTP_USER_AGENT'] or \ - "J-Phone" in request.META['HTTP_USER_AGENT'] or \ - "IEMobile" in request.META['HTTP_USER_AGENT'] or \ - "iPod" in request.META['HTTP_USER_AGENT'] or \ - "iPhone" in request.META['HTTP_USER_AGENT']: - return HttpResponseRedirect( reverse( mobile_mumbles ) ); - else: - return HttpResponseRedirect( reverse( mumbles ) ); + """ Redirect to the servers list. """ + if request.META['HTTP_USER_AGENT'].startswith( 'BlackBerry' ) or \ + "Opera Mobi" in request.META['HTTP_USER_AGENT'] or \ + "Opera Mini" in request.META['HTTP_USER_AGENT'] or \ + "Windows CE" in request.META['HTTP_USER_AGENT'] or \ + "MIDP" in request.META['HTTP_USER_AGENT'] or \ + "Palm" in request.META['HTTP_USER_AGENT'] or \ + "NetFront" in request.META['HTTP_USER_AGENT'] or \ + "Nokia" in request.META['HTTP_USER_AGENT'] or \ + "Symbian" in request.META['HTTP_USER_AGENT'] or \ + "UP.Browser" in request.META['HTTP_USER_AGENT'] or \ + "UP.Link" in request.META['HTTP_USER_AGENT'] or \ + "WinWAP" in request.META['HTTP_USER_AGENT'] or \ + "Android" in request.META['HTTP_USER_AGENT'] or \ + "DoCoMo" in request.META['HTTP_USER_AGENT'] or \ + "KDDI-" in request.META['HTTP_USER_AGENT'] or \ + "Softbank" in request.META['HTTP_USER_AGENT'] or \ + "J-Phone" in request.META['HTTP_USER_AGENT'] or \ + "IEMobile" in request.META['HTTP_USER_AGENT'] or \ + "iPod" in request.META['HTTP_USER_AGENT'] or \ + "iPhone" in request.META['HTTP_USER_AGENT']: + return HttpResponseRedirect( reverse( mobile_mumbles ) ) + else: + return HttpResponseRedirect( reverse( mumbles ) ) def mobile_mumbles( request ): - return mumbles( request, mobile=True ); + return mumbles( request, mobile=True ) def mumbles( request, mobile=False ): - """ Display a list of all configured Mumble servers, or redirect if only one configured. """ - mms = Mumble.objects.all().order_by( "name" ); - - if len(mms) == 1: - return HttpResponseRedirect( reverse( - { False: show, True: mobile_show }[mobile], - kwargs={ 'server': mms[0].id, } - ) ); - - return render_to_response( - 'mumble/%s.html' % { False: 'list', True: 'mobile_list' }[mobile], - { 'MumbleObjects': mms, - 'MumbleActive': True, - }, - context_instance = RequestContext(request) - ); + """ Display a list of all configured Mumble servers, or redirect if only one configured. """ + mms = Mumble.objects.all().order_by( "name" ) + + if len(mms) == 1: + return HttpResponseRedirect( reverse( + { False: show, True: mobile_show }[mobile], + kwargs={ 'server': mms[0].id, } + ) ) + + return render_to_response( + 'mumble/%s.html' % { False: 'list', True: 'mobile_list' }[mobile], + { 'MumbleObjects': mms, + 'MumbleActive': True, + }, + context_instance = RequestContext(request) + ) def show( request, server ): - """ Display the channel list for the given Server ID. - - This includes not only the channel list itself, but indeed the user registration, - server admin and user texture form as well. The template then uses JavaScript - to display these forms integrated into the Channel viewer. - """ - srv = get_object_or_404( Mumble, id=server ); - if not srv.booted: - return render_to_response( - 'mumble/offline.html', - { 'DBaseObject': srv, - 'MumbleActive': True, - }, context_instance = RequestContext(request) ); - - isAdmin = srv.isUserAdmin( request.user ); - - # The tab to start on. - displayTab = 0; - - if isAdmin: - if request.method == 'POST' and "mode" in request.POST and request.POST['mode'] == 'admin': - adminform = MumbleForm( request.POST, instance=srv ); - if adminform.is_valid(): - adminform.save(); - return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) ); - else: - displayTab = 2; - else: - adminform = MumbleForm( instance=srv ); - else: - adminform = None; - - registered = False; - user = None; - - if request.user.is_authenticated(): - # Unregistered users may or may not need a password to register. - if settings.PROTECTED_MODE and srv.passwd: - unregged_user_form = MumbleUserPasswordForm; - # Unregistered users may or may not want to link an existing account - elif settings.ALLOW_ACCOUNT_LINKING: - unregged_user_form = MumbleUserLinkForm; - else: - unregged_user_form = MumbleUserForm; - - - if request.method == 'POST' and 'mode' in request.POST and request.POST['mode'] == 'reg': - try: - user = MumbleUser.objects.get( server=srv, owner=request.user ); - except MumbleUser.DoesNotExist: - regform = unregged_user_form( request.POST ); - regform.server = srv; - if regform.is_valid(): - model = regform.save( commit=False ); - model.owner = request.user; - model.server = srv; - # If we're linking accounts, the change is local only. - model.save( dontConfigureMurmur=( "linkacc" in regform.data ) ); - return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) ); - else: - displayTab = 1; - else: - regform = MumbleUserForm( request.POST, instance=user ); - regform.server = srv; - if regform.is_valid(): - regform.save(); - return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) ); - else: - displayTab = 1; - else: - try: - user = MumbleUser.objects.get( server=srv, owner=request.user ); - except MumbleUser.DoesNotExist: - regform = unregged_user_form(); - else: - regform = MumbleUserForm( instance=user ); - registered = True; - - if request.method == 'POST' and 'mode' in request.POST and request.POST['mode'] == 'texture' and registered: - textureform = MumbleTextureForm( request.POST, request.FILES ); - if textureform.is_valid(): - if 'usegravatar' in textureform.cleaned_data and textureform.cleaned_data['usegravatar'] and user.gravatar: - user.setTextureFromUrl( user.gravatar ); - elif 'texturefile' in request.FILES: - user.setTexture( Image.open( request.FILES['texturefile'] ) ); - return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) ); - else: - textureform = MumbleTextureForm(); - - else: - regform = None; - textureform = None; - - if isAdmin: - if request.method == 'POST' and 'mode' in request.POST: - if request.POST['mode'] == 'kick': - kickform = MumbleKickForm( request.POST ); - if kickform.is_valid(): - if kickform.cleaned_data["ban"]: - srv.banUser( kickform.cleaned_data['session'], kickform.cleaned_data['reason'] ); - srv.kickUser( kickform.cleaned_data['session'], kickform.cleaned_data['reason'] ); - - # ChannelTable is a somewhat misleading name, as it actually contains channels and players. - channelTable = []; - for cid in srv.channels: - if cid != 0 and srv.channels[cid].show: - channelTable.append( srv.channels[cid] ); - for pid in srv.players: - channelTable.append( srv.players[pid] ); - - show_url = reverse( show, kwargs={ 'server': srv.id } ); - login_url = reverse( auth_views.login ); - - return render_to_response( - 'mumble/mumble.html', - { - 'login_url': "%s?next=%s" % ( login_url, show_url ), - 'DBaseObject': srv, - 'ChannelTable': channelTable, - 'CurrentUserIsAdmin': isAdmin, - 'AdminForm': adminform, - 'RegForm': regform, - 'TextureForm': textureform, - 'Registered': registered, - 'DisplayTab': displayTab, - 'MumbleActive': True, - 'MumbleAccount':user, - }, - context_instance = RequestContext(request) - ); + """ Display the channel list for the given Server ID. + + This includes not only the channel list itself, but indeed the user registration, + server admin and user texture form as well. The template then uses JavaScript + to display these forms integrated into the Channel viewer. + """ + srv = get_object_or_404( Mumble, id=server ) + if not srv.booted: + return render_to_response( + 'mumble/offline.html', + { 'DBaseObject': srv, + 'MumbleActive': True, + }, context_instance = RequestContext(request) ) + + isAdmin = srv.isUserAdmin( request.user ) + + # The tab to start on. + displayTab = 0 + + if isAdmin: + if request.method == 'POST' and "mode" in request.POST and request.POST['mode'] == 'admin': + adminform = MumbleForm( request.POST, instance=srv ) + if adminform.is_valid(): + adminform.save() + return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) ) + else: + displayTab = 2 + else: + adminform = MumbleForm( instance=srv ) + else: + adminform = None + + registered = False + user = None + + if request.user.is_authenticated(): + # Unregistered users may or may not need a password to register. + if settings.PROTECTED_MODE and srv.passwd: + unregged_user_form = MumbleUserPasswordForm + # Unregistered users may or may not want to link an existing account + elif settings.ALLOW_ACCOUNT_LINKING: + unregged_user_form = MumbleUserLinkForm + else: + unregged_user_form = MumbleUserForm + + + if request.method == 'POST' and 'mode' in request.POST and request.POST['mode'] == 'reg': + try: + user = MumbleUser.objects.get( server=srv, owner=request.user ) + except MumbleUser.DoesNotExist: + regform = unregged_user_form( request.POST ) + regform.server = srv + if regform.is_valid(): + model = regform.save( commit=False ) + model.owner = request.user + model.server = srv + # If we're linking accounts, the change is local only. + model.save( dontConfigureMurmur=( "linkacc" in regform.data ) ) + return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) ) + else: + displayTab = 1 + else: + regform = MumbleUserForm( request.POST, instance=user ) + regform.server = srv + if regform.is_valid(): + regform.save() + return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) ) + else: + displayTab = 1 + else: + try: + user = MumbleUser.objects.get( server=srv, owner=request.user ) + except MumbleUser.DoesNotExist: + regform = unregged_user_form() + else: + regform = MumbleUserForm( instance=user ) + registered = True + + if request.method == 'POST' and 'mode' in request.POST and request.POST['mode'] == 'texture' and registered: + textureform = MumbleTextureForm( request.POST, request.FILES ) + if textureform.is_valid(): + if 'usegravatar' in textureform.cleaned_data and textureform.cleaned_data['usegravatar'] and user.gravatar: + user.setTextureFromUrl( user.gravatar ) + elif 'texturefile' in request.FILES: + user.setTexture( Image.open( request.FILES['texturefile'] ) ) + return HttpResponseRedirect( reverse( show, kwargs={ 'server': int(server), } ) ) + else: + textureform = MumbleTextureForm() + + else: + regform = None + textureform = None + + if isAdmin: + if request.method == 'POST' and 'mode' in request.POST: + if request.POST['mode'] == 'kick': + kickform = MumbleKickForm( request.POST ) + if kickform.is_valid(): + if kickform.cleaned_data["ban"]: + srv.banUser( kickform.cleaned_data['session'], kickform.cleaned_data['reason'] ) + srv.kickUser( kickform.cleaned_data['session'], kickform.cleaned_data['reason'] ) + + # ChannelTable is a somewhat misleading name, as it actually contains channels and players. + channelTable = [] + for cid in srv.channels: + if cid != 0 and srv.channels[cid].show: + channelTable.append( srv.channels[cid] ) + for pid in srv.players: + channelTable.append( srv.players[pid] ) + + show_url = reverse( show, kwargs={ 'server': srv.id } ) + login_url = reverse( auth_views.login ) + + return render_to_response( 'mumble/mumble.html', { + 'login_url': "%s?next=%s" % ( login_url, show_url ), + 'DBaseObject': srv, + 'ChannelTable': channelTable, + 'CurrentUserIsAdmin': isAdmin, + 'AdminForm': adminform, + 'RegForm': regform, + 'TextureForm': textureform, + 'Registered': registered, + 'DisplayTab': displayTab, + 'MumbleActive': True, + 'MumbleAccount':user, + }, context_instance = RequestContext(request) ) def mobile_show( request, server ): - """ Display the channel list for the given Server ID. """ - - srv = get_object_or_404( Mumble, id=server ); - - user = None; - if request.user.is_authenticated(): - try: - user = MumbleUser.objects.get( server=srv, owner=request.user ); - except MumbleUser.DoesNotExist: - pass; - - return render_to_response( - 'mumble/mobile_mumble.html', - { - 'DBaseObject': srv, - 'MumbleActive': True, - 'MumbleAccount':user, - }, - context_instance = RequestContext(request) - ); - + """ Display the channel list for the given Server ID. """ + srv = get_object_or_404( Mumble, id=server ) + + user = None + if request.user.is_authenticated(): + try: + user = MumbleUser.objects.get( server=srv, owner=request.user ) + except MumbleUser.DoesNotExist: + pass + + return render_to_response( 'mumble/mobile_mumble.html', { + 'DBaseObject': srv, + 'MumbleActive': True, + 'MumbleAccount':user, + }, context_instance = RequestContext(request) ) def showTexture( request, server, userid ): - """ Pack the given user's texture into an HttpResponse. - - If userid is none, use the currently logged in User. - """ - - srv = get_object_or_404( Mumble, id=int(server) ); - user = get_object_or_404( MumbleUser, server=srv, id=int(userid) ); - - try: - img = user.getTexture(); - except ValueError: - raise Http404(); - else: - buf = StringIO(); - img.save( buf, "PNG" ); - return HttpResponse( buf.getvalue(), "image/png" ); + """ Pack the given user's texture into an HttpResponse. + + If userid is none, use the currently logged in User. + """ + + srv = get_object_or_404( Mumble, id=int(server) ) + user = get_object_or_404( MumbleUser, server=srv, id=int(userid) ) + + try: + img = user.getTexture() + except ValueError: + raise Http404() + else: + buf = StringIO() + img.save( buf, "PNG" ) + return HttpResponse( buf.getvalue(), "image/png" ) @login_required def users( request, server ): - """ Create a list of MumbleUsers for a given server serialized as a JSON object. - - If the request has a "data" field, evaluate that and update the user records. - """ - - srv = get_object_or_404( Mumble, id=int(server) ); - - if "resync" in request.POST and request.POST['resync'] == "true": - srv.readUsersFromMurmur(); - - if not srv.isUserAdmin( request.user ): - return HttpResponse( - simplejson.dumps({ 'success': False, 'objects': [], 'errormsg': 'Access denied' }), - mimetype='text/javascript' - ); - - if request.method == 'POST': - data = simplejson.loads( request.POST['data'] ); - for record in data: - if record['id'] == -1: - if record['delete']: - continue; - mu = MumbleUser( server=srv ); - else: - mu = MumbleUser.objects.get( id=record['id'] ); - if record['delete']: - mu.delete(); - continue; - - mu.name = record['name']; - mu.password = record['password']; - if record['owner']: - mu.owner = User.objects.get( id=int(record['owner']) ); - mu.save(); - mu.aclAdmin = record['admin']; - - users = []; - for mu in srv.mumbleuser_set.all(): - owner = None; - if mu.owner is not None: - owner = mu.owner.id - - users.append( { - 'id': mu.id, - 'name': mu.name, - 'password': None, - 'owner': owner, - 'admin': mu.aclAdmin, - } ); - - return HttpResponse( - simplejson.dumps( { 'success': True, 'objects': users } ), - mimetype='text/javascript' - ); + """ Create a list of MumbleUsers for a given server serialized as a JSON object. + + If the request has a "data" field, evaluate that and update the user records. + """ + + srv = get_object_or_404( Mumble, id=int(server) ) + + if "resync" in request.POST and request.POST['resync'] == "true": + srv.readUsersFromMurmur() + + if not srv.isUserAdmin( request.user ): + return HttpResponse( + simplejson.dumps({ 'success': False, 'objects': [], 'errormsg': 'Access denied' }), + mimetype='text/javascript' + ) + + if request.method == 'POST': + data = simplejson.loads( request.POST['data'] ) + for record in data: + if record['id'] == -1: + if record['delete']: + continue + mu = MumbleUser( server=srv ) + else: + mu = MumbleUser.objects.get( id=record['id'] ) + if record['delete']: + mu.delete() + continue + + mu.name = record['name'] + mu.password = record['password'] + if record['owner']: + mu.owner = User.objects.get( id=int(record['owner']) ) + mu.save() + mu.aclAdmin = record['admin'] + + users = [] + for mu in srv.mumbleuser_set.all(): + owner = None + if mu.owner is not None: + owner = mu.owner.id + + users.append( { + 'id': mu.id, + 'name': mu.name, + 'password': None, + 'owner': owner, + 'admin': mu.aclAdmin, + } ) + + return HttpResponse( + simplejson.dumps( { 'success': True, 'objects': users } ), + mimetype='text/javascript' + ) @login_required def djangousers( request ): - """ Return a list of all Django users' names and IDs. """ - - users = [ { 'uid': '', 'uname': '------' } ]; - for du in User.objects.all().order_by( 'username' ): - users.append( { - 'uid': du.id, - 'uname': unicode( du ), - } ); - - return HttpResponse( - simplejson.dumps( { 'success': True, 'objects': users } ), - mimetype='text/javascript' - ); + """ Return a list of all Django users' names and IDs. """ + + users = [ { 'uid': '', 'uname': '------' } ] + for du in User.objects.all().order_by( 'username' ): + users.append( { + 'uid': du.id, + 'uname': unicode( du ), + } ) + + return HttpResponse( + simplejson.dumps( { 'success': True, 'objects': users } ), + mimetype='text/javascript' + ) def mmng_tree( request, server ): - """ Return a JSON representation of the channel tree suitable for - Murmur Manager: - http://github.com/cheald/murmur-manager/tree/master/widget/ - - To make the client widget query this view, set the URL attribute - to "http:///mumble" - """ - - srv = get_object_or_404( Mumble, id=int(server) ); - - chanlist = [] - userlist = [] - - for chanid in srv.channels: - channel = srv.channels[chanid] - - if channel.parent is not None: - parent = channel.parent.chanid - else: - parent = -1 - - chanlist.append({ - "type": "channel", - "id": channel.chanid, - "name": channel.name, - "parent": parent, - "position": channel.position, - "state": channel.temporary and "temporary" or "permanent" - }) - - for sessionid in srv.players: - user = srv.players[sessionid] - userlist.append({ - "type": "player", - "name": user.name, - "channel": user.channel.chanid, - "mute": user.mute or user.selfMute or user.suppress, - "deaf": user.deaf or user.selfDeaf, - "online": user.onlinesecs, - "state": "online" - }) - - if "callback" in request.GET: - prefix = request.GET["callback"] - else: - prefix = "" - - return HttpResponse( - prefix + "(" + simplejson.dumps( { 'channels': chanlist, 'users': userlist } ) + ")", - mimetype='text/javascript' - ); + """ Return a JSON representation of the channel tree suitable for + Murmur Manager: + http://github.com/cheald/murmur-manager/tree/master/widget/ + + To make the client widget query this view, set the URL attribute + to "http:///mumble" + """ + + srv = get_object_or_404( Mumble, id=int(server) ) + + chanlist = [] + userlist = [] + + for chanid in srv.channels: + channel = srv.channels[chanid] + + if channel.parent is not None: + parent = channel.parent.chanid + else: + parent = -1 + + chanlist.append({ + "type": "channel", + "id": channel.chanid, + "name": channel.name, + "parent": parent, + "position": channel.position, + "state": channel.temporary and "temporary" or "permanent" + }) + + for sessionid in srv.players: + user = srv.players[sessionid] + userlist.append({ + "type": "player", + "name": user.name, + "channel": user.channel.chanid, + "mute": user.mute or user.selfMute or user.suppress, + "deaf": user.deaf or user.selfDeaf, + "online": user.onlinesecs, + "state": "online" + }) + + if "callback" in request.GET: + prefix = request.GET["callback"] + else: + prefix = "" + + return HttpResponse( + prefix + "(" + simplejson.dumps( { 'channels': chanlist, 'users': userlist } ) + ")", + mimetype='text/javascript' + ) def mumbleviewer_tree_xml( request, server ): - """ Get the XML tree from the server and serialize it to the client. """ - from xml.etree.cElementTree import tostring as xml_to_string - srv = get_object_or_404( Mumble, id=int(server) ); - return HttpResponse( - xml_to_string( srv.asMvXml(), encoding='utf-8' ), - mimetype='text/xml' - ); + """ Get the XML tree from the server and serialize it to the client. """ + from xml.etree.cElementTree import tostring as xml_to_string + srv = get_object_or_404( Mumble, id=int(server) ) + return HttpResponse( + xml_to_string( srv.asMvXml(), encoding='utf-8' ), + mimetype='text/xml' + ) def mumbleviewer_tree_json( request, server ): - """ Get the Dict from the server and serialize it as JSON to the client. """ - srv = get_object_or_404( Mumble, id=int(server) ); - - if "jsonp_callback" in request.GET: - prefix = request.GET["jsonp_callback"] - else: - prefix = "" - - return HttpResponse( - prefix + "(" + simplejson.dumps( srv.asMvJson() ) + ")", - mimetype='text/javascript' - ); + """ Get the Dict from the server and serialize it as JSON to the client. """ + srv = get_object_or_404( Mumble, id=int(server) ) + + if "jsonp_callback" in request.GET: + prefix = request.GET["jsonp_callback"] + else: + prefix = "" + + return HttpResponse( + prefix + "(" + simplejson.dumps( srv.asMvJson() ) + ")", + mimetype='text/javascript' + ) diff --git a/pyweb/processors.py b/pyweb/processors.py index ed92297..1ba706f 100644 --- a/pyweb/processors.py +++ b/pyweb/processors.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -15,9 +16,9 @@ """ def installed_apps(request): - from django.conf import settings - return { 'ROSETTA_INSTALLED': "rosetta" in settings.INSTALLED_APPS } + from django.conf import settings + return { 'ROSETTA_INSTALLED': "rosetta" in settings.INSTALLED_APPS } def mumble_version(request): - from mumble import version_str - return { 'CURRENTVERSION': version_str } + from mumble import version_str + return { 'CURRENTVERSION': version_str } diff --git a/pyweb/settings.py b/pyweb/settings.py index c8baaa1..aabb0d8 100644 --- a/pyweb/settings.py +++ b/pyweb/settings.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; # Django settings for mumble_django project. """ @@ -56,7 +57,7 @@ MUMBLE_DJANGO_ROOT = None ## from os.path import join, dirname, abspath, exists if not MUMBLE_DJANGO_ROOT or not exists( MUMBLE_DJANGO_ROOT ): - MUMBLE_DJANGO_ROOT = dirname(dirname(abspath(__file__))); + MUMBLE_DJANGO_ROOT = dirname(dirname(abspath(__file__))) # URL Template for constructing Gravatars. GRAVATAR_URL = 'http://www.gravatar.com/avatar/%(hash)s.jpg?d=monsterid&s=%(size)d' @@ -95,8 +96,8 @@ PROTECTED_MODE = False # stolen they could easily take over the server. (So make sure the password can't be easily # guessed, use at least over 9000 letters, blah blah.) # This feature is only available if PROTECTED_MODE is not active. -ALLOW_ACCOUNT_LINKING = True # Allow linking in general? -ALLOW_ACCOUNT_LINKING_ADMINS = False # Allow linking for Admin accounts? +ALLOW_ACCOUNT_LINKING = True # Allow linking in general? +ALLOW_ACCOUNT_LINKING_ADMINS = False # Allow linking for Admin accounts? # Warning and Critical levels for the Munin plugin. These will be multiplied with the # server instance's slot count to calculate the real levels. @@ -155,85 +156,85 @@ MEDIA_URL = MUMBLE_DJANGO_URL+'static/' ADMIN_MEDIA_PREFIX = MUMBLE_DJANGO_URL+'media/' # URL to the login view -LOGIN_URL = MUMBLE_DJANGO_URL + 'accounts/login'; -LOGIN_REDIRECT_URL = MUMBLE_DJANGO_URL + 'accounts/profile'; +LOGIN_URL = MUMBLE_DJANGO_URL + 'accounts/login' +LOGIN_REDIRECT_URL = MUMBLE_DJANGO_URL + 'accounts/profile' # Automatically generate a .secret.txt file containing the SECRET_KEY. # Shamelessly stolen from ByteFlow: try: - SECRET_KEY + SECRET_KEY except NameError: - SECRET_FILE = join(MUMBLE_DJANGO_ROOT, '.secret.txt') - try: - SECRET_KEY = open(SECRET_FILE).read().strip() - except IOError: - try: - from random import choice - SECRET_KEY = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) - secret = file(SECRET_FILE, 'w') - secret.write(SECRET_KEY) - secret.close() - except IOError: - Exception('Please create a %s file with random characters to generate your secret key!' % SECRET_FILE) + SECRET_FILE = join(MUMBLE_DJANGO_ROOT, '.secret.txt') + try: + SECRET_KEY = open(SECRET_FILE).read().strip() + except IOError: + try: + from random import choice + SECRET_KEY = ''.join([choice('abcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*(-_=+)') for i in range(50)]) + secret = file(SECRET_FILE, 'w') + secret.write(SECRET_KEY) + secret.close() + except IOError: + Exception('Please create a %s file with random characters to generate your secret key!' % SECRET_FILE) # List of callables that know how to import templates from various sources. TEMPLATE_LOADERS = ( - 'django.template.loaders.filesystem.load_template_source', - 'django.template.loaders.app_directories.load_template_source', + 'django.template.loaders.filesystem.load_template_source', + 'django.template.loaders.app_directories.load_template_source', ) MIDDLEWARE_CLASSES = ( - 'django.middleware.common.CommonMiddleware', - 'django.contrib.sessions.middleware.SessionMiddleware', - 'django.middleware.locale.LocaleMiddleware', - 'django.contrib.auth.middleware.AuthenticationMiddleware', + 'django.middleware.common.CommonMiddleware', + 'django.contrib.sessions.middleware.SessionMiddleware', + 'django.middleware.locale.LocaleMiddleware', + 'django.contrib.auth.middleware.AuthenticationMiddleware', ) ROOT_URLCONF = 'pyweb.urls' TEMPLATE_DIRS = ( - # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". - # Always use forward slashes, even on Windows. - # Don't forget to use absolute paths, not relative paths. - join( MUMBLE_DJANGO_ROOT, 'pyweb', 'templates' ), + # Put strings here, like "/home/html/django_templates" or "C:/www/django/templates". + # Always use forward slashes, even on Windows. + # Don't forget to use absolute paths, not relative paths. + join( MUMBLE_DJANGO_ROOT, 'pyweb', 'templates' ), ) TEMPLATE_CONTEXT_PROCESSORS = ( - "django.core.context_processors.auth", - "django.core.context_processors.debug", - "django.core.context_processors.i18n", - "django.core.context_processors.media", - 'processors.installed_apps', - 'processors.mumble_version', + "django.core.context_processors.auth", + "django.core.context_processors.debug", + "django.core.context_processors.i18n", + "django.core.context_processors.media", + 'processors.installed_apps', + 'processors.mumble_version', ) TEST_RUNNER = 'mumble.testrunner.run_tests' -TEST_MURMUR_LAB_DIR = join( dirname(MUMBLE_DJANGO_ROOT), 'murmur' ); -TEST_MURMUR_FILES_DIR = join( MUMBLE_DJANGO_ROOT, 'testdata' ); +TEST_MURMUR_LAB_DIR = join( dirname(MUMBLE_DJANGO_ROOT), 'murmur' ) +TEST_MURMUR_FILES_DIR = join( MUMBLE_DJANGO_ROOT, 'testdata' ) -CONVERSIONSQL_ROOT = join( MUMBLE_DJANGO_ROOT, "pyweb", "mumble", "conversionsql" ); +CONVERSIONSQL_ROOT = join( MUMBLE_DJANGO_ROOT, "pyweb", "mumble", "conversionsql" ) INSTALLED_APPS = [ - 'django.contrib.auth', - 'django.contrib.admin', - 'django.contrib.contenttypes', - 'django.contrib.sessions', - 'django.contrib.sites', - 'registration', - 'mumble', + 'django.contrib.auth', + 'django.contrib.admin', + 'django.contrib.contenttypes', + 'django.contrib.sessions', + 'django.contrib.sites', + 'registration', + 'mumble', ] def modprobe( name ): - """ Try to import the named module, and if that works add it to INSTALLED_APPS. """ - try: - __import__( name ) - except ImportError: - pass - else: - INSTALLED_APPS.append( name ) + """ Try to import the named module, and if that works add it to INSTALLED_APPS. """ + try: + __import__( name ) + except ImportError: + pass + else: + INSTALLED_APPS.append( name ) # Check if rosetta is available. # http://code.google.com/p/django-rosetta diff --git a/pyweb/urls.py b/pyweb/urls.py index 65e64a7..4d0e299 100644 --- a/pyweb/urls.py +++ b/pyweb/urls.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -26,32 +27,32 @@ handler404 = 'django.views.defaults.page_not_found' handler500 = 'django.views.defaults.server_error' urlpatterns = patterns('', - (r'^/?$', 'mumble.views.redir' ), + (r'^/?$', 'mumble.views.redir' ), - # Uncomment the admin/doc line below and add 'django.contrib.admindocs' - # to INSTALLED_APPS to enable admin documentation: - # (r'^admin/doc/', include('django.contrib.admindocs.urls')), + # Uncomment the admin/doc line below and add 'django.contrib.admindocs' + # to INSTALLED_APPS to enable admin documentation: + # (r'^admin/doc/', include('django.contrib.admindocs.urls')), - (r'^accounts/profile/', 'views.profile' ), - (r'^accounts/imprint/', 'views.imprint' ), - (r'^accounts/', include('registration.urls') ), + (r'^accounts/profile/', 'views.profile' ), + (r'^accounts/imprint/', 'views.imprint' ), + (r'^accounts/', include('registration.urls') ), - (r'^mumble/', include('mumble.urls')), + (r'^mumble/', include('mumble.urls')), - # Uncomment the next line to enable the admin: - (r'^admin/', admin.site.urls), + # Uncomment the next line to enable the admin: + (r'^admin/', admin.site.urls), ) if "rosetta" in settings.INSTALLED_APPS: - urlpatterns += patterns( '', - ( r'rosetta/', include( 'rosetta.urls' ) ) - ) + urlpatterns += patterns( '', + ( r'rosetta/', include( 'rosetta.urls' ) ) + ) # Development stuff if settings.DEBUG or True: - urlpatterns += patterns('', - (r'^%s(?P.*)$' % settings.MEDIA_URL[1:], - 'django.views.static.serve', - {'document_root': settings.MEDIA_ROOT, 'show_indexes': True} ), - ) + urlpatterns += patterns('', + (r'^%s(?P.*)$' % settings.MEDIA_URL[1:], + 'django.views.static.serve', + {'document_root': settings.MEDIA_ROOT, 'show_indexes': True} ), + ) diff --git a/pyweb/views.py b/pyweb/views.py index ffdc20e..c6f2d7c 100644 --- a/pyweb/views.py +++ b/pyweb/views.py @@ -1,4 +1,5 @@ # -*- coding: utf-8 -*- +# kate: space-indent on; indent-width 4; replace-tabs on; """ * Copyright © 2009-2010, Michael "Svedrin" Ziegler @@ -14,29 +15,29 @@ * GNU General Public License for more details. """ -from django.shortcuts import render_to_response -from django.template import RequestContext -from django.contrib.auth.decorators import login_required +from django.shortcuts import render_to_response +from django.template import RequestContext +from django.contrib.auth.decorators import login_required -from mumble.models import MumbleUser +from mumble.models import MumbleUser @login_required def profile( request ): - userdata = { - "ProfileActive": True, - "mumbleaccs": MumbleUser.objects.filter( owner = request.user ), - }; - - return render_to_response( - 'registration/profile.html', - userdata, - context_instance = RequestContext(request) - ); + userdata = { + "ProfileActive": True, + "mumbleaccs": MumbleUser.objects.filter( owner = request.user ), + } + + return render_to_response( + 'registration/profile.html', + userdata, + context_instance = RequestContext(request) + ) def imprint( request ): - import mumble - return render_to_response( - 'registration/imprint.html', - { 'upstreamversion': mumble.getLatestUpstreamVersion() }, - context_instance = RequestContext(request) ); + import mumble + return render_to_response( + 'registration/imprint.html', + { 'upstreamversion': mumble.getLatestUpstreamVersion() }, + context_instance = RequestContext(request) )