changeset 532:22942728c43c

Work on getting the server to use ElementTree
author digitalxero
date Tue, 06 Oct 2009 16:03:42 -0600
parents a724e89143b2
children 38eaf9f14f3e
files orpg/networking/mplay_client.py orpg/networking/mplay_messaging.py orpg/networking/mplay_server.py
diffstat 3 files changed, 1098 insertions(+), 1740 deletions(-) [+]
line wrap: on
line diff
--- a/orpg/networking/mplay_client.py	Mon Oct 05 22:03:24 2009 -0600
+++ b/orpg/networking/mplay_client.py	Tue Oct 06 16:03:42 2009 -0600
@@ -43,6 +43,7 @@
 from struct import pack, unpack, calcsize
 from orpg.tools.orpg_log import logger
 from orpg.mapper.images import ImageHandler
+from orpg.networking.mplay_messaging import messaging
 
 
 try:
@@ -175,7 +176,7 @@
         # Wait to be told it's okay to start running
         self.startedEvent.wait()
 
-        while( self.get_status() == MPLAY_CONNECTED ):
+        while(self.get_status() == MPLAY_CONNECTED):
             readMsg = self.recvMsg(self.sock)
             try:
                 if self.useCompression and self.compressionType != None:
@@ -330,17 +331,14 @@
 # The IP field should really be deprecated as too many systems are NAT'd and/or behind firewalls for a
 # client provided IP address to have much value.  As such, we now label it as deprecated.
     def toxml(self,action):
-        el = Element('player')
-        el.set('name', myescape(self.name))
-        el.set('action', action)
-        el.set('id', self.id)
-        el.set('group_id', self.group_id)
-        el.set('ip', self.ip)
-        el.set('status', self.text_status)
-        el.set('version', self.version)
-        el.set('protocol_version', self.protocol_version)
-        el.set('client_string', self.client_string)
-        el.set('useCompression', str(self.useCompression))
+        el = messaging.build('player', name=myescape(self.name),
+                                     action=action, id=self.id,
+                                     group_id=self.group_id, ip=self.ip,
+                                     status=self.text_status,
+                                     version=self.version,
+                                     protocol_version=self.protocol_version,
+                                     client_string=self.client_string,
+                                     useCompression=str(self.useCompression))
 
         cmpType = 'None'
         if cmpBZ2 and (self.compressionType == 'Undefined' or self.compressionType == bz2):
@@ -544,8 +542,7 @@
             return (1, self.players[id][2], name)
 
     def boot_player(self, id, boot_pwd = ""):
-        el = Element('boot')
-        el.set('boot_pwd', boot_pwd)
+        el = messaging.build('boot', boot_pwd=boot_pwd)
         self.send(el, id)
 
 #---------------------------------------------------------
@@ -553,12 +550,8 @@
 #---------------------------------------------------------
 
     def set_room_pass(self, npwd, pwd=""):
-        el = Element('alter')
-        el.set('key', 'pwd')
-        el.set('val', npwd)
-        el.set('bpw', pwd)
-        el.set('plr', self.id)
-        el.set('gid', self.group_id)
+        el = messaging.build('alter', key='pwd', val=npwd, bpw=pwd,
+                                     plr=self.id, gid=self.group_id)
 
         self.outbox.put(el)
         self.update()
@@ -566,12 +559,8 @@
     def set_room_name(self, name, pwd=""):
         name = name.replace('&', '&').replace('"', '&quote;').replace("'",
                                                                     ''')
-        el = Element('alter')
-        el.set('key', 'name')
-        el.set('val', name)
-        el.set('bpw', pwd)
-        el.set('plr', self.id)
-        el.set('gid', self.group_id)
+        el = messaging.build('alter', key='name', val=name, bpw=pwd,
+                                     plr=self.id, gid=self.group_id)
 
         self.outbox.put(el)
         self.update()
@@ -581,68 +570,59 @@
 #---------------------------------------------------------
 
     def display_roles(self):
-        el = Element('role')
-        el.set('action', 'display')
-        el.set('player', self.id)
-        el.set('group_id', self.group_id)
+        el = messaging.build('role', action='display', player=elf.id,
+                                     group_id=self.group_id)
 
         self.outbox.put(el)
 
     def get_role(self):
-        el = Element('role')
-        el.set('action', 'get')
-        el.set('player', self.id)
-        el.set('group_id', self.group_id)
+        el = messaging.build('role', action='get', player=self.id,
+                                     group_id=self.group_id)
 
         self.outbox.put(el)
 
     def set_role(self, player, role, pwd=""):
-        el = Element('role')
-        el.set('action', 'set')
-        el.set('player', player)
-        el.set('role', role)
-        el.set('boot_pwd', pwd)
-        el.set('group_id', self.group_id)
+        el = messaging.build('role', action='set', player=player,
+                                     role=role, boot_pwd=pwd,
+                                     group_id=self.group_id)
 
         self.outbox.put(el)
         self.update()
 
     def send(self, msg, player="all"):
         if self.status == MPLAY_CONNECTED and player != self.id:
-            el = Element('msg')
-            el.set('to', player)
-            el.set('from', self.id)
-            el.set('group_id', self.group_id)
-            try:
-                el1 = fromstring(msg)
-                el.append(el1)
-            except ExpatError:
-                el.text = msg
+            el = messaging.build('msg', to=player, _from=self.id,
+                                         group_id=self.group_id)
+            if iselement(msg):
+                el.append(msg)
+            else:
+                try:
+                    el1 = fromstring(msg)
+                    el.append(el1)
+                except ExpatError:
+                    el.text = msg
+
             self.outbox.put(el)
         self.check_my_status()
 
     def send_sound(self, snd_xml):
+        #FIXME#
         if self.status == MPLAY_CONNECTED:
             self.outbox.put(snd_xml)
         self.check_my_status()
 
     def send_create_group(self, name, pwd, boot_pwd, minversion):
-        el = Element('create_group')
-        el.set('from', self.id)
-        el.set('pwd', pwd)
-        el.set('name', name)
-        el.set('boot_pwd', boot_pwd)
-        el.set('min_version', minversion)
+        el = messaging.build('create_group', _from=self.id, pwd=pwd,
+                                     name=name, boot_pwd=boot_pwd,
+                                     min_version=minversion)
         self.outbox.put(el)
 
     def send_join_group(self, group_id, pwd):
         if (group_id != 0):
             self.update_role("Lurker")
 
-        el = Element('join_group')
-        el.set('from', self.id)
-        el.set('pwd', pwd)
-        el.set('group_id', str(group_id))
+        el = messaging.build('join_group', _from=self.id, pwd=pwd,
+                                     group_id=group_id)
         self.outbox.put(el)
 
     def poll(self, evt=None):
@@ -688,19 +668,7 @@
         if len(data) < 5:
             return
 
-        try:
-            el = fromstring(data)
-        except ExpatError:
-            end = data.find(">")
-            head = data[:end+1]
-            msg = data[end+1:]
-            el = fromstring(head)
-            try:
-                el1 = fromstring(msg)
-                el.append(el1)
-            except ExpatError:
-                el.text = msg
-                logger.general("Bad Message: \n" + data)
+        el = messaging.parse(data)
 
         id = el.get('from') or el.get('id', '0')
         # see if we are ignoring this user
@@ -869,7 +837,7 @@
 
             self.sendMsg(self.sock, outgoing)
             data = self.recvMsg(self.sock)
-            print data
+
             # get new id and group_id
             el = fromstring(data)
             self.id = el.get('id')
--- a/orpg/networking/mplay_messaging.py	Mon Oct 05 22:03:24 2009 -0600
+++ b/orpg/networking/mplay_messaging.py	Tue Oct 06 16:03:42 2009 -0600
@@ -1,474 +1,64 @@
-# Copyright (C) 2000-2001 The OpenRPG Project
-#
-#        openrpg-dev@lists.sourceforge.net
-#
-# This program is free software; you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation; either version 2 of the License, or
-# (at your option) any later version.
-#
-# This program is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
-# --
-#
-# File: mplay_messaging.py
-# Author: Dj Gilcrease
-# Maintainer:
-# Version:
-#   $Id: mplay_messaging.py,v 1.5 2007/05/06 16:42:59 digitalxero Exp $
-#
-# Description: This file contains the code for the client / server messaging
-#
-
-__version__ = "$Id: mplay_messaging.py,v 1.5 2007/05/06 16:42:59 digitalxero Exp $"
-
-import socket
-import Queue
-import thread
-import traceback
-from threading import Event, Lock
-from xml.sax.saxutils import escape
-from struct import pack, unpack, calcsize
-from string import *
-from orpg.orpg_version import *
-import os
-import time
-
-from orpg.orpgCore import *
-from orpg.tools.settings import settings
-from orpg.tools.orpgLog import logger
-from orpg.tools.decorators import depreciated
-
-def myescape(data):
-    return escape(data,{"\"":""})
-
-class messenger:
-    def __init__(self, *args, **kwargs):
-        self.xml = open_rpg.get_component("xml")
-
-        if kwargs.has_key('isServer'):
-            self.isServer = kwargs['isServer']
-        else:
-            self.isServer = False
-
-        self.listen_event = Event()
-        self.outbox = Queue.Queue(0)
-        self.inbox_event = Event()
-        self.inbox = Queue.Queue(0)
-        self.startedEvent = Event()
-        self.exitEvent = Event()
-        self.sendThreadExitEvent = Event()
-        self.recvThreadExitEvent = Event()
-        self.port = int(settings.get("port"))
-        self.ip = socket.gethostbyname(socket.gethostname())
-        self.lensize = calcsize('i')
-        self.mplay_type = ('disconnected', 'connected', 'disconnecting', 'group change', 'group change failed')
-        self.status = self.mplay_type[0]
-        self.alive = False
-        self.sock = None
-        self.version = VERSION
-        self.protocol_version = PROTOCOL_VERSION
-        self.client_string = CLIENT_STRING
-        self.minClientVersion = SERVER_MIN_CLIENT_VERSION
-        self.id = "0"
-        self.group_id = "0"
-        self.name = ""
-        self.role = "GM"
-        self.ROLE_GM = "GM"
-        self.ROLE_PLAYER = "PLAYER"
-        self.ROLE_LURKER = "LURKER"
-        self.text_status = "Idle"
-        self.statLock = Lock()
-        self.useroles = 0
-        self.lastmessagetime = time.time()
-        self.connecttime = time.time()
-        self.timeout_time = None
-        self.ignorelist = {}
-        self.players = {}
-        self.groups = {}
+from orpg.external.etree.ElementTree import ElementTree, Element
+from orpg.external.etree.ElementTree import fromstring, tostring, iselement
+from xml.parsers.expat import ExpatError
 
-        #Setup Stuff from the Server
-        if kwargs.has_key('inbox'):
-            self.inbox = kwargs['inbox']
-        if kwargs.has_key('sock'):
-            self.sock = kwargs['sock']
-        if kwargs.has_key('ip'):
-            self.ip = kwargs['ip']
-        if kwargs.has_key('role'):
-            self.role = kwargs['role']
-        if kwargs.has_key('id'):
-            self.id = kwargs['id']
-        if kwargs.has_key('group_id'):
-            self.group_id = kwargs['group_id']
-        if kwargs.has_key('name'):
-            self.name = kwargs['name']
-        if kwargs.has_key('version'):
-            self.version = kwargs['version']
-        if kwargs.has_key('protocol_version'):
-            self.protocol_version = kwargs['protocol_version']
-        if kwargs.has_key('client_string'):
-            self.client_string = kwargs['client_string']
-
-    def build_message(self, *args, **kwargs):
-        message = '<' + args[0]
-
-        #Setup the attributes of the message
-        if len(kwargs) > 0:
-            for attrib in kwargs.keys():
-                message += ' ' + attrib + '="' + str(kwargs[attrib]) + '"'
-
-        #Add the actual message if there is one
-        if len(args) > 1:
-            #Close the first part
-            message += '>'
-            message += escape(args[1])
-
-            #Close the whole thing
-            message += '</' + args[0] + '>'
-        else:
-            message += ' />'
-        return message
-
-    def disconnect(self):
-        self.set_status(2)
-
-        try:
-            self.sock.shutdown( 2 )
-        except Exception:
-            logger.exception(traceback.format_exc())
-
-        self.set_status(0)
-
-    def reset(self, sock):
-        self.disconnect()
-        self.sock = sock
-        self.initialize_threads()
-
-    def update_role(self, role):
-        self.useroles = 1
-        self.role = role
-
-    def use_roles(self):
-        return self.useroles
+class MessageingClass(object):
+    """
+    This is used by the Client and server to build outgoing messages
+    It does not allow building of nested message as needed by the gametree
+    or map, but that can be achive via a for or while loop is needed
 
-    def update_self_from_player(self, player):
-        try:
-            (self.name, self.ip, self.id, self.text_status, self.version, self.protocol_version, self.client_string,role) = player
-        except Exception:
-            logger.exception(traceback.format_exc())
-
-    @depreciated
-    def toxml(self, act):
-        xml_data = self.build_message('player',
-                                name=myescape(self.name),
-                                action=act,
-                                id=self.id,
-                                group_id=self.group_id,
-                                ip=self.ip,
-                                status=self.text_status,
-                                version=self.version,
-                                protocol_version=self.protocol_version,
-                                client_string=self.client_string
-                                )
-        return xml_data
-
-    def get_status(self):
-        self.statLock.acquire()
-        status = self.status
-        self.statLock.release()
-        return status
-
-    def my_role(self):
-        return self.role
-
-    def set_status(self, status):
-        self.statLock.acquire()
-        self.status = status
-        self.statLock.release()
-
-    def initialize_threads(self):
-        "Starts up our threads (2) and waits for them to make sure they are running!"
-        self.status = 'connected'
-        self.sock.setblocking(1)
-
-        # Confirm that our threads have started
-        thread.start_new_thread( self.sendThread,(0,) )
-        thread.start_new_thread( self.recvThread,(0,) )
-        self.startedEvent.set()
-
-    def __str__(self):
-        return "%s(%s)\nIP:%s\ngroup_id:%s\n%s (%s)" % (self.name, self.id, self.ip, self.group_id, self.idle_time(), self.connected_time())
-
-    # idle time functions added by snowdog 3/31/04
-    def update_idle_time(self):
-        self.lastmessagetime = time.time()
-
-    def idle_time(self):
-        curtime = time.time()
-        idletime = curtime - self.lastmessagetime
-        return idletime
-
-    def idle_status(self):
-        idletime = self.idle_time()
-        idlemins = idletime / 60
-        status = "Unknown"
-        if idlemins < 3:
-            status = "Active"
-        elif idlemins < 10:
-            status = "Idle ("+str(int(idlemins))+" mins)"
-        else:
-            status = "Inactive ("+str(int(idlemins))+" mins)"
-        return status
-
-    def connected_time(self):
-        curtime = time.time()
-        timeoffset = curtime - self.connecttime
-        return timeoffset
+    All systems that want to parse a message should use the parse method
+    """
+    def __new__(cls):
+        it = cls.__dict__.get("__it__")
+        if it is not None:
+            return it
+        cls.__it__ = it = object.__new__(cls)
+        return it
 
-    def connected_time_string(self):
-        "returns the time client has been connected as a formated time string"
-        ct = self.connected_time()
-        d = int(ct/86400)
-        h = int( (ct-(86400*d))/3600 )
-        m = int( (ct-(86400*d)-(3600*h))/60)
-        s = int( (ct-(86400*d)-(3600*h)-(60*m)) )
-        cts =  zfill(d,2)+":"+zfill(h,2)+":"+zfill(m,2)+":"+zfill(s,2)
-        return cts
-
-    def clear_timeout(self):
-        self.timeout_time = None
-
-    def check_time_out(self):
-        if self.timeout_time==None:
-            self.timeout_time = time.time()
-        curtime = time.time()
-        diff = curtime - self.timeout_time
-        if diff > 1800:
-            return 1
-        else:
-            return 0
-
-    def send(self, msg):
-        if self.get_status() == 'connected':
-            self.outbox.put(msg)
-
-    def change_group(self, group_id, groups):
-        old_group_id = str(self.group_id)
-        groups[group_id].add_player(self.id)
-        groups[old_group_id].remove_player(self.id)
-        self.group_id = group_id
-        self.outbox.put(self.toxml('group'))
-        msg = groups[group_id].game_map.get_all_xml()
-        self.send(msg)
-        return old_group_id
-
-    def take_dom(self, xml_dom):
-        self.name = xml_dom.getAttribute("name")
-        self.text_status = xml_dom.getAttribute("status")
-
-    def add_msg_handler(self, tag, function, core=False):
-        if not self.msg_handlers.has_key(tag):
-            self.msg_handlers[tag] = function
-            if core:
-                self.core_msg_handlers.append(tag)
-        else:
-            logger.info(XML Messages ' + tag + ' already has a handler', True)
-
-    def remove_msg_handler(self, tag):
-        if self.msg_handlers.has_key(tag) and not tag in self.core_msg_handlers:
-            del self.msg_handlers[tag]
-        else:
-            logger.info('XML Messages ' + tag + ' already deleted', True)
-
+    def build(self, tag, *args, **kwargs):
+        """
+        Builds a simple ElementTree Element
+        args:
+            tag - This is the elements tag
+            *arg - used to build the text of the element
+                These are combined via ' '.join(args)
+            **kwargs - used to define the attributes
+                kwargs that start with _ have the _ stripped
+                this is to all attributes like from
+                if you really need the _ you need to do __
+        """
+        el = Element(tag)
+        for attr, value in kwargs.iteritems():
+            if not isinstance(value, basestring):
+                value = str(value)
 
-    #Message Handaling
-    def message_handler(self, arg):
-        xml_dom = None
-        logger.note("message handler thread running...")
-        while self.alive or self.status == 'connected':
-            data = None
-            try:
-                data = self.inbox.get(0)
-            except Queue.Empty:
-                time.sleep(0.25) #sleep 1/4 second
-                continue
-            bytes = len(data)
-            if bytes < 5:
-                continue
-            try:
-                thread.start_new_thread(self.parse_incoming_dom,(str(data),))
-                #data has been passed... unlink from the variable references
-                #so data in passed objects doesn't change (python passes by reference)
-                del data
-                data = None
-            except Exception, e:
-                logger.general(traceback.format_exc())
-                if xml_dom: xml_dom.unlink()
-        if xml_dom: xml_dom.unlink()
-        logger.note("message handler thread exiting...")
-        self.inbox_event.set()
+            if attr.startswith('_'):
+                attr = attr[1:]
 
-    def parse_incoming_dom(self, data):
-        xml_dom = None
-        try:
-            xml_dom = self.xml.parseXml(data)
-            xml_dom = xml_dom._get_documentElement()
-            self.message_action(xml_dom, data)
-
-        except Exception, e:
-            logger.exception("Error in parse of inbound message. Ignoring message.")
-            logger.exception("\tOffending data(" + str(len(data)) + "bytes)=" + data)
-            logger.exception(traceback.format_exc())
-        if xml_dom: xml_dom.unlink()
-
-    def message_action(self, xml_dom, data):
-        tag_name = xml_dom._get_tagName()
-        if self.msg_handlers.has_key(tag_name):
-            self.msg_handlers[tag_name](xml_dom, data)
-        else:
-            logger.general("Unknown Message Type")
-            logger.general(data)
-
-        #Message Action thread expires and closes here.
-        return
-
-    #Privet functions
-    def sendThread( self, arg ):
-        "Sending thread.  This thread reads from the data queue and writes to the socket."
-        # Wait to be told it's okay to start running
-        self.startedEvent.wait()
-
-        # Loop as long as we have a connection
-        while( self.get_status() == 'connected' ):
-            try:
-                readMsg = self.outbox.get( block=1 )
-
-            except Exception, text:
-                logger.exception("messenger->sendThread():  " + str(text))
+            el.set(attr, value)
 
-            # If we are here, it's because we have data to send, no doubt!
-            if self.status == 'connected':
-                try:
-                    # Send the entire message, properly formated/encoded
-                    sent = self.sendMsg( self.sock, readMsg )
-                except:
-                    logger.exception(traceback.format_exc())
-
-        self.sendThreadExitEvent.set()
-
-    def sendMsg( self, sock, msg ):
-        """Very simple function that will properly encode and send a message to te
-        remote on the specified socket."""
-
-        # Calculate our message length
-        length = len( msg )
-
-        # Encode the message length into network byte order
-        lp = pack( 'i', socket.htonl( length ) )
-
-        try:
-            # Send the encoded length
-            sentl = sock.send( lp )
+        if len(args):
+            el.text = ' '.join(args)
 
-            # Now, send the message the the length was describing
-            sentm = sock.send( msg )
-            if self.isServer:
-                logger.debug("('data_sent', " + str(sentl+sentm) + ")")
-            return sentm
-        except socket.error, e:
-            logger.exception(traceback.format_exc())
-        except:
-            logger.exception(traceback.format_exc())
-
-    def recvThread( self, arg ):
-        "Receiving thread.  This thread reads from the socket and writes to the data queue."
-
-        # Wait to be told it's okay to start running
-        self.startedEvent.wait()
-        while( self.get_status() == 'connected' ):
-            readMsg = self.recvMsg( self.sock )
-
-            # Make sure we didn't get disconnected
-            bytes = len( readMsg )
-            if bytes == 0:
-                break
-
-            # Check the length of the message
-            bytes = len( readMsg )
-
-            # Make sure we are still connected
-            if bytes == 0:
-                break
-            else:
-                # Pass along the message so it can be processed
-                self.inbox.put( readMsg )
-                self.update_idle_time() #update the last message time
+        return el
 
-        if bytes == 0:
-            logger.note("Remote has disconnected!")
-            self.set_status(2)
-
-        self.outbox.put( "" )    # Make sure the other thread is woken up!
-        self.sendThreadExitEvent.set()
-
-    def recvData( self, sock, readSize ):
-        """Simple socket receive method.  This method will only return when the exact
-        byte count has been read from the connection, if remote terminates our
-        connection or we get some other socket exception."""
-        data = ""
-        offset = 0
+    def parse(self, raw_msg):
         try:
-            while offset != readSize:
-                frag = sock.recv( readSize - offset )
-
-                # See if we've been disconnected
-                rs = len( frag )
-                if rs <= 0:
-                    # Loudly raise an exception because we've been disconnected!
-                    raise IOError, "Remote closed the connection!"
-                else:
-                    # Continue to build complete message
-                    offset += rs
-                    data += frag
-        except socket.error, e:
-            logger.exception("Socket Error: messenger->recvData(): " +  str(e))
-            data = ""
-        return data
+            el = fromstring(raw_msg)
+        except ExpatError:
+            end = raw_msg.find(">")
+            head = raw_msg[:end+1]
+            msg = raw_msg[end+1:]
+            el = fromstring(head)
+            try:
+                el1 = fromstring(msg)
+                el.append(el1)
+            except ExpatError:
+                el.text = msg
+                logger.general("Bad Message: \n" + data)
 
-    def recvMsg( self, sock ):
-        """This method now expects to receive a message having a 4-byte prefix length.  It will ONLY read
-        completed messages.  In the event that the remote's connection is terminated, it will throw an
-        exception which should allow for the caller to more gracefully handles this exception event.
-
-        Because we use strictly reading ONLY based on the length that is told to use, we no longer have to
-        worry about partially adjusting for fragmented buffers starting somewhere within a buffer that we've
-        read.  Rather, it will get ONLY a whole message and nothing more.  Everything else will remain buffered
-        with the OS until we attempt to read the next complete message."""
-
-        msgData = ""
-        try:
-            lenData = self.recvData( sock, self.lensize )
+        return el
 
-            # Now, convert to a usable form
-            (length,) = unpack( 'i', lenData )
-            length = socket.ntohl( length )
-
-            # Read exactly the remaining amount of data
-            msgData = self.recvData( sock, length )
-
-            if self.isServer:
-                logger.debug("('data_recv', " + str(length+4) + ")")
-        except Exception:
-            logger.exception(traceback.format_exc())
-
-        return msgData
-
-if __name__ == "__main__":
-    test = messenger(None)
-    print test.build_message('hello', "This is a test message", attrib1="hello world", attrib2="hello world2", attrib3="hello world3")
+messaging = MessageingClass()
\ No newline at end of file
--- a/orpg/networking/mplay_server.py	Mon Oct 05 22:03:24 2009 -0600
+++ b/orpg/networking/mplay_server.py	Tue Oct 06 16:03:42 2009 -0600
@@ -19,6 +19,7 @@
 # --
 #
 # File: mplay_server.py
+#!/usr/bin/env python
 # Author: Chris Davis
 # Maintainer:
 # Version:
@@ -31,9 +32,8 @@
 
 # 04-15-2005 [Snowdog]: Added patch from Brandan Yares (xeriar). Reference: patch tracker id #1182076
 
-__version__ = "$Id: mplay_server.py,v 1.155 2008/01/24 03:52:03 digitalxero Exp $"
-
-#!/usr/bin/env python
+from __future__ import with_statement
+
 """
 <msg to='' from='' group_id='' />
 <player id='' ip='' group_id='' name='' action='new,del,group,update' status="" version=""/>
@@ -65,9 +65,14 @@
 from orpg.tools.settings import settings
 from orpg.tools.orpg_log import logger
 from orpg.tools.decorators import debugging
+from orpg.networking.mplay_messaging import messaging
 
 from orpg.external import json
 
+from orpg.external.etree.ElementTree import ElementTree, Element
+from orpg.external.etree.ElementTree import fromstring, tostring, iselement
+from xml.parsers.expat import ExpatError
+
 # Import the minidom XML module
 from xml.dom import minidom
 
@@ -83,7 +88,8 @@
 
 
 class game_group(object):
-    def __init__( self, id, name, pwd, desc="", boot_pwd="", minVersion="", mapFile=None, messageFile=None, persist =0 ):
+    def __init__(self, id, name, pwd, desc="", boot_pwd="", minVersion="",
+                  mapFile=None, messageFile=None, persist=0):
         self.id = id
         self.name = name
         self.desc = desc
@@ -118,7 +124,6 @@
             f.write(self.game_map.get_all_xml())
             f.close()
 
-
     def add_player(self,id):
         self.players.append(id)
 
@@ -135,7 +140,6 @@
         tmp = self.players
         return tmp
 
-
     def check_pwd(self,pwd):
         return (pwd==self.pwd)
 
@@ -160,15 +164,15 @@
             return 0
         return 1
 
+    def has_pwd(self):
+        return self.pwd != ""
+
     #depreciated - see send_group_list()
-    def toxml(self,act="new"):
-        #  Please don't add the boot_pwd to the xml, as this will give it away to players watching their console
-        xml_data = "<group id=\"" + self.id
-        xml_data += "\" name=\"" + self.name
-        xml_data += "\" pwd=\"" + str(self.pwd!="")
-        xml_data += "\" players=\"" + str(self.get_num_players())
-        xml_data += "\" action=\"" + act + "\" />"
-        return xml_data
+    def toxml(self, act="new"):
+        el = messaging.build('group', id=self.id, name=self.name,
+                             pwd=self.has_pwd(), action=act,
+                             players=self.get_num_players())
+        return el
 
 
 
@@ -206,26 +210,34 @@
         else:
             return 0
 
-    def send(self,msg,player,group):
+    def send(self, msg, player, group):
         if self.get_status() == MPLAY_CONNECTED:
-            self.outbox.put("<msg to='" + player + "' from='0' group_id='" + group + "' />" + msg)
-
-    def change_group(self,group_id,groups):
+            el = messaging.build('msg', to=player, _from='0',
+                                         group_id=self.group_id)
+            try:
+                el1 = fromstring(msg)
+                el.append(el1)
+            except ExpatError:
+                el.text = msg
+
+            self.outbox.put(el)
+
+    def change_group(self, group_id, groups):
         old_group_id = str(self.group_id)
         groups[group_id].add_player(self.id)
         groups[old_group_id].remove_player(self.id)
         self.group_id = group_id
         self.outbox.put(self.toxml('group'))
         msg = groups[group_id].game_map.get_all_xml()
-        self.send(msg,self.id,group_id)
+        self.send(msg, self.id, group_id)
         return old_group_id
 
-    def self_message(self,act):
-        self.send(act,self.id,self.group_id)
-
-    def take_dom(self,xml_dom):
-        self.name = xml_dom.getAttribute("name")
-        self.text_status = xml_dom.getAttribute("status")
+    def self_message(self, act):
+        self.send(act, self.id, self.group_id)
+
+    def take_dom(self, etreeEl):
+        self.name = etreeEl.get("name")
+        self.text_status = etreeEl.get("status")
 
 
 ######################################################################
@@ -286,7 +298,10 @@
 
         # Since the server is just starting here, we read in the XML configuration
         # file.  Notice the lobby is still created here by default.
-        self.groups = { '0': game_group('0','Lobby','','The game lobby', '', '', self.userPath + self.lobbyMapFile, self.userPath + self.lobbyMessageFile, 1)}
+        self.groups = {'0': game_group('0','Lobby','','The game lobby', '',
+                                        '', self.userPath + self.lobbyMapFile,
+                                        self.userPath + self.lobbyMessageFile,
+                                        1)}
         # Make sure the server's name gets set, in case we are being started from
         # elsewhere.  Basically, if it's passed in, we'll over ride what we were
         # prompted for.  This should never really happen at any rate.
@@ -294,12 +309,14 @@
         self.initServerConfig()
         self.listen_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
         self.listen_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
-        self.listen_thread = thread.start_new_thread(self.listenAcceptThread, (0,))
-        self.in_thread = thread.start_new_thread(self.message_handler,(0,))
+        self.listen_thread = thread.start_new_thread(self.listenAcceptThread,
+                                                     (0,))
+        self.in_thread = thread.start_new_thread(self.message_handler, (0,))
 
         #  Starts the player reaper thread.  See self.player_reaper_thread_func() for more explanation
-        self.player_reaper_thread = thread.start_new_thread(self.player_reaper_thread_func,(0,))
-        thread.start_new_thread(self.PluginThread,())
+        self.player_reaper_thread = thread.start_new_thread(
+            self.player_reaper_thread_func, (0,))
+        thread.start_new_thread(self.PluginThread, ())
         self.svrcmds = {}
         self.initsvrcmds()
         self.ban_list = {}
@@ -334,18 +351,12 @@
 
         # try to use it.
         try:
-            self.banDom = minidom.parse(self.userPath + 'ban_list.xml')
-            self.banDom.normalize()
-            self.banDoc = self.banDom.documentElement
-
-            for element in self.banDom.getElementsByTagName('banned'):
-                playerName = element.getAttribute( 'name' ).replace("&", "&amp;").replace("<", "&lt;").replace('"', "&quot;").replace(">", "&gt;")
-                playerIP = element.getAttribute('ip')
-                self.ban_list[playerIP] = {}
-                self.ban_list[playerIP]['ip'] = playerIP
-                self.ban_list[playerIP]['name'] = playerName
-                self.log_msg(str(playerName) + " " + str(playerIP) + " is banned.")
-            self.saveBanList()
+            with open(dir_struct['user'] + 'ban_list.xml') as f:
+                bl = fromstring(f.read())
+
+            for banned in bl.findall('banned'):
+                ip = banned.get('ip')
+                self.ban_list[ip] = banned.get('name')
         except Exception, e:
             self.log_msg("Exception in initBanList() " + str(e))
 
@@ -355,14 +366,13 @@
 
         # try to use it.
         try:
-            data = []
-            data.append("<server>\n")
-            for ip in self.ban_list:
-                data.append('    <banned name="' + str(self.ban_list[ip]['name'].replace("&amp;", "&").replace("&lt;", "<").replace("&quot;", '"').replace("&gt;", ">")) + '" ip="' + str(self.ban_list[ip]['ip']) + '" />' + "\n")
-            data.append("</server>")
-            file = open(self.userPath + self.banFile ,"w")
-            file.write("".join(data))
-            file.close()
+            root = messaging.build('server')
+            for ip, name in self.ban_list.iteritems():
+                banned = messaging.build('banned', name=name, ip=ip)
+                root.append(banned)
+
+            with open(dir_struct['user'] + self.banFile ,"w") as f:
+                f.write(tostring(root))
         except Exception, e:
             self.log_msg("Exception in saveBanList() " + str(e))
 
@@ -374,9 +384,8 @@
         validate.config_file("server_ini.xml", "default_server_ini.xml")
         # try to use it.
         try:
-            self.configDom = minidom.parse(self.userPath + 'server_ini.xml')
-            self.configDom.normalize()
-            self.configDoc = self.configDom.documentElement
+            with open(dir_struct['user'] + 'server_ini.xml') as f:
+                config = fromstring(f.read())
 
             if hasattr(self, 'bootPassword'):
                 # if we are coming from the gui server just take the password that was passed in,
@@ -384,16 +393,12 @@
                 self.boot_pwd = self.bootPassword
             else:
                 # Obtain the lobby/server password if it's been specified
-                if self.configDoc.hasAttribute("admin"):
-                    self.boot_pwd = self.configDoc.getAttribute("admin")
-                elif self.configDoc.hasAttribute("boot"):
-                    self.boot_pwd = self.configDoc.getAttribute("boot")
+                self.boot_pwd = config.get('admin') or config.get('boot', '')
 
                 if len(self.boot_pwd) < 1:
                     self.boot_pwd = raw_input("Enter admin password:  ")
 
-            if self.configDoc.hasAttribute("register") and not self.reg:
-                self.reg = self.configDoc.getAttribute("register")
+            self.reg = self.reg or config.get('register', '')
 
             if not len(self.reg) > 0 or self.reg[0].upper() not in ("Y", "N"):
                 opt = raw_input("Do you want to post your server to the OpenRPG Meta Server list? (y,n) ")
@@ -402,252 +407,187 @@
                 else:
                     self.reg = 'N'
 
-            LobbyName = 'Lobby'
-            if self.configDoc.hasAttribute("lobbyname"):
-                LobbyName = self.configDoc.getAttribute("lobbyname")
-
-            map_node = self.configDoc.getElementsByTagName("map")[0]
-            msg_node = self.configDoc.getElementsByTagName("message")[0]
-            mapFile = map_node.getAttribute('file')
-            msgFile = msg_node.getAttribute('file')
-            if mapFile == '':
-                mapFile = 'Lobby_map.xml'
-            if msgFile == '':
-                msgFile = 'LobbyMessage.html'
+            LobbyName = config.get('lobbyname', 'Lobby')
+
+            map_node = config.iter_find('map')
+            msg_node = config.iter_find('message')
+            mapFile = map_node.get('file',
+                                   'Lobby_map.xml').replace("myfiles/", "")
+            msgFile = msg_node.get('file',
+                                   'LobbyMessage.html').replace("myfiles/", "")
+
             # Update the lobby with the passwords if they've been specified
             if len(self.boot_pwd):
-                self.groups = {'0': game_group( '0', LobbyName, "", 'The game lobby', self.boot_pwd, "",
-                                                self.userPath + mapFile.replace("myfiles/", ""),
-                                                self.userPath + msgFile.replace("myfiles/", ""), 1 )
-                               }
+                self.groups = {'0': game_group('0', LobbyName, "",
+                                               'The game lobby', self.boot_pwd,
+                                               "", dir_struct['user'] + mapFile,
+                                                dir_struct['user'] + msgFile,
+                                                1)}
 
             # set ip or dns name to send to meta server
-            service_node = self.configDoc.getElementsByTagName("service")[0]
-            address = service_node.getAttribute("address")
+            service_node = config.iter_find('service')
+            address = service_node.get('address')
             address = address.lower()
-            if address == "" or address == "hostname/address" or address == "localhost":
+            if address in ["", "hostname/address", "localhost"]:
                 self.server_address = None
             else:
                 self.server_address = address
-            self.server_port = OPENRPG_PORT
-            if service_node.hasAttribute("port"):
-                self.server_port = int(service_node.getAttribute("port"))
-            if self.configDoc.hasAttribute("name") and len(self.configDoc.getAttribute("name")) > 0 :
-                self.serverName = self.configDoc.getAttribute("name")
-            else:
-                if self.reg[0].upper() == "Y":
-                    if not self.serverName:
-                        self.serverName = raw_input("Server Name? ")
-                    self.register()
+            self.server_port = int(service_node.get("port", OPENRPG_PORT))
+
+            self.serverName = config.get('name')
+
+            if self.reg[0].upper() == "Y":
+                if not self.serverName:
+                    self.serverName = raw_input("Server Name? ")
+                self.register()
+
 
             # Get the minimum openrpg version from config if available
             # if it isn't set min version to internal default.
             #
             # server_ini.xml entry for version tag...
             # <version min="x.x.x">
-            try:
-                mver = self.configDoc.getElementsByTagName("version")[0]
-                self.minClientVersion = mver.getAttribute("min")
-            except:
-                self.minClientVersion = SERVER_MIN_CLIENT_VERSION #from orpg/orpg_version.py
+            mver = config.iter_find('version')
+            if mver is not None:
+                self.minClientVersion = mver.get('min',
+                                                 SERVER_MIN_CLIENT_VERSION)
+            else:
+                self.minClientVersion = SERVER_MIN_CLIENT_VERSION
+
             self.defaultMessageFile = ""
-            # This try/except bit is to allow older versions of python to continue without a list error.
-
-
-
-            #------------------------[ START <AUTOKICK> TAG PROCESSING ]--------------
+            #-----------[ START <AUTOKICK> TAG PROCESSING ]--------------
             # Auto-kick option defaults for silent booting and
             # setting the default zombie-client delay time --Snowdog 9/05
             #
             # server_ini.xml entry for autikick tag...
             # <autokick silent=["no","yes"] delay="(# of seconds)">
-
-            try:
-                ak = self.configDoc.getElementsByTagName("autokick")[0]
-                if ak.hasAttribute("silent"):
-                    if ((ak.getAttribute("silent")).lower() == "yes"):
-                        self.silent_auto_kick = 1
-                    else:
-                        self.silent_auto_kick = 0
-                if ak.hasAttribute("delay"):
-                    try:
-                        delay = int(ak.getAttribute("delay"))
-                        self.zombie_time = delay
-                    except:
-                        #delay value cannot be converted into an int use defaut
-                        self.zombie_time = 480 #(default 8 mins)
-                        self.log_msg("**WARNING** Error with autokick delay string using default (480 sec)")
-
-            except:
-                self.silent_auto_kick = 0 #(default to off)
-                self.zombie_time = 480 #(default 8 mins)
-                self.log_msg("**WARNING** Error loading autokick settings... using defaults")
+            ak = config.iter_find('autokick')
+            self.silent_auto_kick = False
+            self.zombie_time = 480
+            if ak is not None:
+                self.silent_auto_kick = False
+                if ak.get('silent', 'no').lower() == 'yes':
+                    self.silent_auto_kick = True
+                self.zombie_time = int(ak.get('delay', 480))
 
             alk = ""
-            if (self.silent_auto_kick == 1): alk = "(Silent Mode)"
+            if self.silent_auto_kick:
+                alk = "(Silent Mode)"
             self.log_msg("Auto Kick:  Delay="+str(self.zombie_time) + " " + alk)
-            #------------------------[ END <AUTOKICK> TAG PROCESSING ]--------------
-
-
-
-            #-------------------------------[ START <ROOM_DEFAULT> TAG PROCESSING ]--------------------
-            #
+            #------------------[ END <AUTOKICK> TAG PROCESSING ]--------------
+
+
+
+            #--------------[ START <ROOM_DEFAULT> TAG PROCESSING ]----------
             # New room_defaults configuration option used to set various defaults
             # for all user created rooms on the server. Incorporates akomans older
             # default room message code (from above)      --Snowdog 11/03
             #
             # option syntax
-            # <room_defaults passwords="yes" map="myfiles/LobbyMap.xml" message="myfiles/LobbyMessage.html" />
+            # <room_defaults passwords="yes" map="myfiles/LobbyMap.xml"
+            # message="myfiles/LobbyMessage.html" />
 
             #default settings for tag options...
-            roomdefault_msg = str(self.defaultMessageFile) #no message is the default
-            roomdefault_map = "" #use lobby map as default
-            roomdefault_pass = 1 #allow passwords
+            roomdefault_msg = str(self.defaultMessageFile)
+            roomdefault_map = ""
+            roomdefault_pass = True
 
 
             #pull information from config file DOM
-            try:
-                roomdefaults = self.configDom.getElementsByTagName("room_defaults")[0]
-                #rd.normalize()
-                #roomdefaults = self.rd.documentElement
-                try:
-                    setting = roomdefaults.getElementsByTagName('passwords')[0]
-                    rpw = setting.getAttribute('allow')
-                    if rpw == "no" or rpw == "0":
-                        roomdefault_pass = 0
-                        self.log_msg("Room Defaults: Disallowing Passworded Rooms")
-                    else:
-                        self.log_msg("Room Defaults: Allowing Passworded Rooms")
-                except:
-                    self.log_msg("Room Defaults: [Warning] Allowing Passworded Rooms")
-                try:
-                    setting = roomdefaults.getElementsByTagName('map')[0]
-                    map = setting.getAttribute('file')
-                    if map != "":
-                        roomdefault_map = self.userPath + map.replace("myfiles/", "")
-                        self.log_msg("Room Defaults: Using " + str(map) + " for room map")
-                except:
-                    self.log_msg("Room Defaults: [Warning] Using Default Map")
-
-                try:
-                    setting = roomdefaults.getElementsByTagName('message')[0]
-                    msg = setting.getAttribute('file')
-                    if msg != "":
-                        if msg[:4].lower() == 'http':
-                            roomdefault_msg = msg
-                        else:
-                            roomdefault_msg = self.userPath + msg.replace("myfiles/", "")
-                        self.log_msg("Room Defaults: Using " + str(msg) + " for room messages")
-                except:
-                    print ("Room Defaults: [Warning] Using Default Message")
-            except:
-                traceback.print_exc()
-                self.log_msg("**WARNING** Error loading default room settings from configuration file. Using internal defaults.")
+            roomdefaults = config.iter_find('room_defaults')
+            rs = roomdefaults.iter_find('passwords')
+            if rs is not None and rs.get('allow', 'yes').lower() in ['no',
+                                                                     '0']:
+                roomdefault_pass = False
+
+            rs = roomdefaults.iter_find('map')
+            if rs is not None and rs.get('file'):
+                new = rs.get('file').replace("myfiles/", "")
+                roomdefault_map = dir_struct['user'] + new
+
+            rs = roomdefaults.iter_find('message')
+            if rs is not None and rs.get('file'):
+                new = rs.get('file').replace("myfiles/", "")
+                roomdefault_msg = dir_struct['user'] + new
 
 
             #set the defaults
-            if roomdefault_msg != "" or roomdefault_msg != None:
-                self.defaultMessageFile = roomdefault_msg  #<room_defaults> tag superceeds older <newrooms> tag
-            else:
-                self.defaultMessageFile = None
-
-            if roomdefault_map != "" or roomdefault_map != None:
-                self.defaultMapFile = roomdefault_map  #<room_defaults> tag superceeds older <newrooms> tag
-            else:
-                self.defaultMapFile = None
-
-            ##### room default map not handled yet. SETTING IGNORED
-            if roomdefault_pass == 0: self.allow_room_passwords = 0
-            else: self.allow_room_passwords = 1
-
-            #-------------------------------[ END <ROOM_DEFAULT> TAG PROCESSING ]--------------------
-
+            self.defaultMessageFile = roomdefault_msg or None
+            self.defaultMapFile = roomdefault_map or None
+            self.allow_room_passwords = roomdefault_pass or False
+            #----------[ END <ROOM_DEFAULT> TAG PROCESSING ]----------
 
             ###Server Cheat message
-            try:
-                cheat_node = self.configDoc.getElementsByTagName("cheat")[0]
-                self.cheat_msg = cheat_node.getAttribute("text")
-            except:
-                self.cheat_msg = "**FAKE ROLL**"
-                self.log_msg("**WARNING** <cheat txt=\"\"> tag missing from server configuration file. Using empty string.")
-
-
+            self.cheat_msg = "**FAKE ROLL**"
+            cheat_node = config.iter_find('cheat')
+            if cheat_node is not None:
+                self.cheat_msg = cheat_node.get('text', '**FAKE ROLL**')
 
             # should validate protocal
-            validate_protocol_node = self.configDom.getElementsByTagName("validate_protocol ")
-
-            self.validate_protocol = 1
-
-            if(validate_protocol_node):
-                self.validate_protocol = (validate_protocol_node[0].getAttribute("value") == "True")
-            if(self.validate_protocol != 1):
-                self.log_msg("Protocol Validation: OFF")
-            self.makePersistentRooms()
+            vp_node = config.iter_find('validate_protocol')
+
+            self.validate_protocol = True
+
+            if vp_node is not None:
+                if vp_node.get('value').lower() not in ['true', 'yes', '1']:
+                    self.validate_protocol = False
+
+            self.makePersistentRooms(config)
 
             self.log_msg("Server Configuration File: Processing Completed.")
 
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg("Exception in initServerConfig() " + str(e))
 
 
-    def makePersistentRooms(self):
-        'Creates rooms on the server as defined in the server config file.'
-
-        for element in self.configDom.getElementsByTagName('room'):
-            roomName = element.getAttribute('name')
-            roomPassword = element.getAttribute('password')
-            bootPassword = element.getAttribute('boot')
-
-            # Conditionally check for minVersion attribute
-            if element.hasAttribute('minVersion'):
-                minVersion = element.getAttribute('minVersion')
-            else:
-                minVersion = ""
-
-            # Extract the map filename attribute from the map node
-            # we only care about the first map element found -- others are ignored
-            mapElement = element.getElementsByTagName('map')[0]
-            mapFile = self.userPath + mapElement.getAttribute('file').replace("myfiles/", "")
-
-            messageElement = element.getElementsByTagName('message')[0]
-            messageFile = messageElement.getAttribute('file')
-
-            if messageFile[:4] != 'http':
-                messageFile = self.userPath + messageFile.replace("myfiles/", "")
-
-            # Make sure we have a message to even mess with
-            if(len(messageFile) == 0):
-                messageFile = self.defaultMessageFile
-
-            if(len(mapFile) == 0):
-                mapFile = self.defaultMapFile
-
-            moderated = 0
-            if element.hasAttribute('moderated') and element.getAttribute('moderated').lower() == "true":
-                moderated = 1
+    def makePersistentRooms(self, config):
+        """
+        Creates rooms on the server as defined in the server config file.
+        """
+        for room in config.findall('room'):
+            name = room.get('name')
+            pwd = room.get('password')
+            admin = room.get('admin') or room.get('boot')
+            minVer = room.get('minVersion', '')
+            mapFile = self.defaultMapFile
+            msgFile = self.defaultMessageFile
+            moderated = False
+
+            map_node = room.iter_find('map')
+            if map_node is not None:
+                mapFile = dir_struct['user'] + map_node.get('file').\
+                        replace("myfiles/", "")
+
+            msg_node = room.iter_find('message')
+            if msg_node is not None:
+                msgFile = msg_node.get('file')
+
+                if msgFile[:4] != 'http':
+                    msgFile = dir_struct['user'] + msgFile.\
+                                replace("myfiles/", "")
+
+            if room.get('moderated', '').lower() in ['true', 'yes', '1']:
+                moderated = True
 
             #create the new persistant group
-            self.new_group(roomName, roomPassword, bootPassword, minVersion, mapFile, messageFile, persist = 1, moderated=moderated)
-
-
+            self.new_group(name, pwd, admin, minVer, mapFile, msgFile,
+                           persist=True, moderated=moderated)
 
     def isPersistentRoom(self, id):
-        'Returns True if the id is a persistent room (other than the lobby), otherwise, False.'
-
-        # altered persistance tracking from simple room id based to per-group setting
-        # allows arbitrary rooms to be marked as persistant without needing the self.persistRoomThreshold
-        # -- Snowdog 4/04
+        """
+        Returns True if the id is a persistent room (other than the lobby), otherwise, False.
+        """
         try:
-            id = str(id) #just in case someone sends an int instead of a str into the function
-            if id not in self.groups: return 0 #invalid room, can't be persistant
+            id = str(id)
+            if id not in self.groups:
+                return False
+
             pr = (self.groups[id]).persistant
             return pr
         except:
             self.log_msg("Exception occured in isPersistentRoom(self,id)")
-            return 0
-
-
+            return False
 
     #-----------------------------------------------------
     #  Toggle Meta Logging  -- Added by Snowdog 4/03
@@ -664,7 +604,7 @@
     #-----------------------------------------------------
     #  Start/Stop Network Logging to File  -- Added by Snowdog 4/03
     #-----------------------------------------------------
-    def NetworkLogging(self, mode = 0):
+    def NetworkLogging(self, mode=0):
         if mode == 0:
             self.log_msg("Network Logging: OFF")
             self.log_network_messages = 0
@@ -681,16 +621,19 @@
                 self.players[n].EnableMessageLogging = mode
             except:
                 self.log_msg("Error changing Message Logging Mode for client #" + str(self.players[n].id))
+
     def NetworkLoggingStatus(self):
-        if self.log_network_messages == 0: return "Network Traffic Log: Off"
-        elif self.log_network_messages == 1: return "Network Traffic Log: Logging (composite file)"
-        elif self.log_network_messages == 2: return "Network Traffic Log: Logging (inbound/outbound files)"
-        else: self.log_msg("Network Traffic Log: [Unknown]")
-
-
-
-
-    def register_callback(instance, xml_dom = None,source=None):
+        if self.log_network_messages == 0:
+            return "Network Traffic Log: Off"
+        elif self.log_network_messages == 1:
+            return "Network Traffic Log: Logging (composite file)"
+        elif self.log_network_messages == 2:
+            return "Network Traffic Log: Logging (inbound/outbound files)"
+        else:
+            self.log_msg("Network Traffic Log: [Unknown]")
+
+    def register_callback(instance, xml_dom=None, source=None):
+        #FIXME#
         if xml_dom:    # if we get something
             if source == getMetaServerBaseURL():    # if the source of this DOM is the authoritative meta
                 try:
@@ -782,15 +725,20 @@
 
         #  iterate through the currently running metas and prune any
         #  not currently listed in the Meta Server list.
-        if self.show_meta_messages != 0: self.log_msg( "Checking running register threads for outdated metas.")
+        if self.show_meta_messages != 0:
+            self.log_msg( "Checking running register threads for outdated metas.")
         for meta in self.metas.keys():
-            if self.show_meta_messages != 0: self.log_msg("meta:" + meta + ": ")
+            if self.show_meta_messages != 0:
+                self.log_msg("meta:" + meta + ": ")
+
             if not meta in metalist:  # if the meta entry running is not in the list
-                if self.show_meta_messages != 0: self.log_msg( "Outdated.  Unregistering and removing")
+                if self.show_meta_messages != 0:
+                    self.log_msg( "Outdated.  Unregistering and removing")
                 self.metas[meta].unregister()
                 del self.metas[meta]
             else:
-                if self.show_meta_messages != 0: self.log_msg( "Found in current meta list.  Leaving intact.")
+                if self.show_meta_messages != 0:
+                    self.log_msg( "Found in current meta list.  Leaving intact.")
 
         #  Now call register() for alive metas or start one if we need one
         for meta in metalist:
@@ -805,8 +753,6 @@
         self.be_registered = 1
         thread.start_new_thread(self.registerRooms,(0,))
 
-
-
     def unregister(self):
         #  loop through all existing meta entries
         #  Don't rely on getMetaServers(), as a server may have been
@@ -821,154 +767,125 @@
 
         self.be_registered = 0
 
-
-
-
     #  This method runs as it's own thread and does the group_member_check every
     #    sixty seconds.  This should eliminate zombies that linger when no one is
     #    around to spook them.  GC: Frequency has been reduced as I question how valid
     #    the implementation is as it will only catch a very small segment of lingering
     #    connections.
-    def player_reaper_thread_func(self,arg):
+    def player_reaper_thread_func(self, arg):
         while self.alive:
             time.sleep(60)
 
-            self.p_lock.acquire()
-            for group in self.groups.keys():
-                self.check_group_members(group)
-            self.p_lock.release()
+            with self.p_lock:
+                for group in self.groups.keys():
+                    self.check_group_members(group)
 
     #This thread runs ever 250 miliseconds, and checks various plugin stuff
     def PluginThread(self):
         while self.alive:
-            self.p_lock.acquire()
-            players = ServerPlugins.getPlayer()
-
-            for player in players:
-                if player is not None:
-                    #Do something here so they can show up in the chat room for non web users'
-                    pass
-
-            data = ServerPlugins.preParseOutgoing()
-
-            for msg in data:
-                try:
-                    xml_dom = parseXml(msg)
-                    xml_dom = xml_dom._get_documentElement()
-
-                    if xml_dom.hasAttribute('from') and int(xml_dom.getAttribute('from')) > -1:
-                        xml_dom.setAttribute('from', '-1')
-
-                    xml_dom.setAttribute('to', 'all')
-                    self.incoming_msg_handler(xml_dom, msg)
-                    xml_dom.unlink()
-                except:
-                    pass
-
-            self.p_lock.release()
-            time.sleep(0.250)
-
-
-    def sendMsg( self, sock, msg, useCompression=False, cmpType=None):
+            with self.p_lock:
+                players = ServerPlugins.getPlayer()
+
+                for player in players:
+                    if player is not None:
+                        #Do something here so they can show up in the chat room for non web users'
+                        pass
+
+                data = ServerPlugins.preParseOutgoing()
+
+                for msg in data:
+                    try:
+                        el = messaging.parse(msg)
+
+                        if el.get('from') and int(el.get('from')) > -1:
+                            el.set('from', '-1')
+
+                        el.set('to', 'all')
+                        self.incoming_msg_handler(el, msg)
+                    except:
+                        pass
+
+            time.sleep(0.0250)
+
+    def sendMsg(self, sock, msg, useCompression=False, cmpType=None):
         """Very simple function that will properly encode and send a message to te
         remote on the specified socket."""
 
+        if iselement(msg):
+            msg = tostring(msg)
+
         if useCompression and cmpType != None:
-            mpacket = cmpType.compress(msg)
-            lpacket = pack('!i', len(mpacket))
-            sock.send(lpacket)
-
-            offset = 0
-            while offset < len(mpacket):
-                slice = buffer(mpacket, offset, len(mpacket)-offset)
-                sent = sock.send(slice)
-                offset += sent
-            sentm = offset
-        else:
-            # Calculate our message length
-            length = len( msg )
-
-            # Encode the message length into network byte order
-            lp = pack('!i', length)
-
-            try:
-                # Send the encoded length
-                sentl = sock.send( lp )
-
-                # Now, send the message the the length was describing
-                sentm = sock.send( msg )
-
-            except socket.error, e:
-                self.log_msg( e )
-
-            except Exception, e:
-                self.log_msg( e )
-
-
-    def recvData( self, sock, readSize ):
+            msg = cmpType.compress(msg)
+
+        lpacket = pack('!i', len(msg))
+        sock.send(lpacket)
+
+        offset = 0
+        while offset < len(msg):
+            slice = buffer(msg, offset, len(msg)-offset)
+            sent = sock.send(slice)
+            offset += sent
+
+    def recvData(self, sock, readSize):
         """Simple socket receive method.  This method will only return when the exact
         byte count has been read from the connection, if remote terminates our
         connection or we get some other socket exception."""
-
         data = ""
         offset = 0
         try:
             while offset != readSize:
-                frag = sock.recv( readSize - offset )
-
+                frag = sock.recv(readSize - offset)
                 # See if we've been disconnected
                 rs = len( frag )
                 if rs <= 0:
                     # Loudly raise an exception because we've been disconnected!
                     raise IOError, "Remote closed the connection!"
-
                 else:
                     # Continue to build complete message
                     offset += rs
                     data += frag
-
         except socket.error, e:
-            self.log_msg("Socket Error: recvData(): " +  e )
+            self.log_msg(e)
             data = ""
-
         return data
 
-
-
     def recvMsg(self, sock, useCompression=False, cmpType=None):
-        """This method now expects to receive a message having a 4-byte prefix length.  It will ONLY read
-        completed messages.  In the event that the remote's connection is terminated, it will throw an
-        exception which should allow for the caller to more gracefully handles this exception event.
-
-        Because we use strictly reading ONLY based on the length that is told to use, we no longer have to
-        worry about partially adjusting for fragmented buffers starting somewhere within a buffer that we've
-        read.  Rather, it will get ONLY a whole message and nothing more.  Everything else will remain buffered
-        with the OS until we attempt to read the next complete message."""
+        """This method now expects to receive a message having a
+        4-byte prefix length.  It will ONLY read completed messages.
+        In the event that the remote's connection is terminated, it will
+        throw an exception which should allow for the caller to more
+        gracefully handles this exception event.
+
+        Because we use strictly reading ONLY based on the length that is
+        told to use, we no longer have to worry about partially adjusting
+        for fragmented buffers starting somewhere within a buffer that we've
+        read.  Rather, it will get ONLY a whole message and nothing more.
+        Everything else will remain buffered with the OS until we attempt to
+        read the next complete message."""
 
         msgData = ""
         try:
-            lenData = self.recvData( sock, MPLAY_LENSIZE )
+            lenData = self.recvData(sock, MPLAY_LENSIZE)
 
             # Now, convert to a usable form
             (length,) = unpack('!i', lenData)
 
             # Read exactly the remaining amount of data
-            msgData = self.recvData( sock, length )
+            msgData = self.recvData(sock, length)
 
             try:
                 if useCompression and cmpType != None:
                     msgData = cmpType.decompress(msgData)
-            except:
-                traceback.print_exc()
+            except Exception:
+                logger.exception(traceback.format_exc())
 
         except Exception, e:
-            self.log_msg( "Exception: recvMsg(): " + str(e) )
+            self.log_msg("Exception: recvMsg(): " + str(e))
 
         return msgData
 
-
-
     def kill_server(self):
+        #FIXME#
         self.alive = 0
         self.log_msg("Server stopping...")
         self.unregister()                    # unregister from the Meta
@@ -995,17 +912,14 @@
         self.incoming_event.wait(10)
         self.log_msg("Server stopped!")
 
-
-
-    def log_msg(self,msg):
+    def log_msg(self, msg):
         if self.log_to_console:
-            if self.log_console:
-                self.log_console(msg)
-            else:
-                print str(msg)
-
+            logger.note(msg, True)
+        else:
+            logger.note(msg)
 
     def print_help(self):
+        #FIXME#
         print
         print "Commands: "
         print "'kill' or 'quit' - to stop the server"
@@ -1028,8 +942,6 @@
         print "'monitor (#)' - monitors raw network I/O stream to specific client"
         print "'purge clients' - boots all connected clients off the server immediately"
         print "'zombie [set [min]]' - view/set the auto-kick time for zombie clients"
-        #drop any clients that are idle for more than 8 hours
-        #as these are likely dead clientskick' - kick a player from the server"
         print "'uptime' - reports how long server has been running"
         print "'roompasswords' - allow/disallow room passwords (toggle)"
         print "'search' - will prompt for pattern and display results"
@@ -1040,44 +952,41 @@
         print "'help' or '?' or 'h' - for this help message"
         print
 
-
-    def broadcast(self,msg):
-        self.send_to_all("0","<msg to='all' from='0' group_id='1'><font color='#FF0000'>" + msg + "</font>")
-
+    def broadcast(self, msg):
+        msg = '<font color="#FF0000">' + msg + '</font>'
+        el = messaging.build('msg', msg, to='all', _from='0',
+                                     group_id='1')
+        self.send_to_all(el)
 
     def console_log(self):
-        if self.log_to_console == 1:
+        self.log_to_console = not self.log_to_console
+        if not self.log_to_console:
             print "console logging now off"
-            self.log_to_console = 0
         else:
             print "console logging now on"
-            self.log_to_console = 1
-
 
     def groups_list(self):
-        self.p_lock.acquire()
-        try:
-            keys = self.groups.keys()
-            for k in keys:
-                pw = "-"
-                pr = " -"
-                if self.groups[k].pwd != "":
-                    pw = "P"
-                if self.isPersistentRoom( k ):
-                    pr = " S" #using S for static (P for persistant conflicts with password)
-                print "Group: " + k + pr + pw + '  Name: ' + self.groups[k].name
-            print
-
-        except Exception, e:
-            self.log_msg(str(e))
-
-        self.p_lock.release()
-
-#----------------------------------------------------------------
-#  Monitor Function  -- Added by snowdog 2/05
-#----------------------------------------------------------------
+        #CLEANUP#
+        with self.p_lock:
+            try:
+                keys = self.groups.keys()
+                for k in keys:
+                    pw = "-"
+                    pr = " -"
+                    if self.groups[k].pwd != "":
+                        pw = "P"
+                    if self.isPersistentRoom( k ):
+                        pr = " S" #using S for static (P for persistant conflicts with password)
+                    print "Group: " + k + pr + pw + '  Name: ' + self.groups[k].name
+                print
+
+            except Exception, e:
+                self.log_msg(str(e))
+
     def monitor(self, pid, mode=1 ):
-        "allows monitoring of a specific user(s) network i/o"
+        """
+        allows monitoring of a specific user(s) network i/o
+        """
         #if mode is not set to 1 then monitor adds toggles the state
         #of monitoring on the given user
 
@@ -1090,8 +999,7 @@
             self.log_msg("Monitor: Mode=" + str(r) + " on Player #" + str(pid))
         except:
             self.log_msg("Monitor: Invalid Player ID")
-            traceback.print_exc()
-
+            logger.exception(traceback.format_exc())
 
     def search(self,patern):
         keys = self.groups.keys()
@@ -1123,237 +1031,218 @@
                 elif self.players[id].client_string.find(patern)>-1:
                     self.print_player_info(self.players[id])
 
-
     def print_player_info(self,player):
+        #CLEANUP#
         print player.id,player.name,player.ip,player.group_id, player.role,player.version,player.protocol_version,player.client_string
 
-    #----------------------------------------------------------------
-    #  Uptime Function  -- Added by snowdog 4/03
-    #----------------------------------------------------------------
     def uptime(self , mode = 0):
-        "returns string containing how long server has been in operation"
+        """
+        returns string containing how long server has been in operation
+        """
         ut = time.time() - self.server_start_time
         d = int(ut/86400)
         h = int( (ut-(86400*d))/3600 )
         m = int( (ut-(86400*d)-(3600*h))/60)
         s = int( (ut-(86400*d)-(3600*h)-(60*m)) )
         uts =  str( "This server has been running for:\n " + str(d) + " days  " + str(h) + " hours  " + str(m) + " min. " + str(s) + " sec.  [" + str(int(ut)) + " seconds]")
-        if mode == 0: print uts
-        else: return uts
-
-    #-----------------------------------------------------
-    #  Toggle Room Password Allow  -- Added by Snowdog 11/03
-    #-----------------------------------------------------
+        if mode == 0:
+            logger.debug(uts, True)
+        else:
+            return uts
+
     def RoomPasswords(self):
-        if self.allow_room_passwords != 0:
-            self.allow_room_passwords = 0
+        self.allow_room_passwords = not self.allow_room_passwords
+        if not self.allow_room_passwords:
             return "Client Created Room Passwords: Disallowed"
         else:
-            self.allow_room_passwords = 1
             return "Client Created Room Passwords: Allowed"
 
 
     def group_dump(self,k):
-        self.p_lock.acquire()
-        try:
-            print "Group: " + k
-            print "    Name:  %s" % self.groups[k].name
-            print "    Desc:  %s" % self.groups[k].desc
-            print "    Pass:  %s" % self.groups[k].pwd
-            print "    Boot:  %s" % self.groups[k].boot_pwd
-            print "    Moderated:  %s" % self.groups[k].moderated
-            print "    Map:  %s" % self.groups[k].game_map.get_all_xml()
-            print
-        except Exception, e:
-            self.log_msg(str(e))
-        self.p_lock.release()
-
-    #----------------------------------------------------------------
-    #  Player List  -- Added by snowdog 4/03
-    #----------------------------------------------------------------
+        #CLEANUP#
+        with self.p_lock:
+            try:
+                print "Group: " + k
+                print "    Name:  %s" % self.groups[k].name
+                print "    Desc:  %s" % self.groups[k].desc
+                print "    Pass:  %s" % self.groups[k].pwd
+                print "    Boot:  %s" % self.groups[k].boot_pwd
+                print "    Moderated:  %s" % self.groups[k].moderated
+                print "    Map:  %s" % self.groups[k].game_map.get_all_xml()
+                print
+            except Exception, e:
+                self.log_msg(str(e))
+
     def player_list(self):
-        "display a condensed list of players on the server"
-        self.p_lock.acquire()
-        try:
-            print "------------[ PLAYER LIST ]------------"
-            keys = self.groups.keys()
-            keys.sort(id_compare)
-            for k in keys:
-                groupstring = "Group " + str(k)  + ": " +  self.groups[k].name
-                if self.groups[k].pwd != "":
-                    groupstring += " (Pass: \"" + self.groups[k].pwd + "\" )"
-                print groupstring
-                ids = self.groups[k].get_player_ids()
-                ids.sort(id_compare)
-                for id in ids:
-                    if self.players.has_key(id):
-                        print "  (%s)%s [IP: %s] %s (%s)" % ((self.players[id]).id, (self.players[id]).name, (self.players[id]).ip, (self.players[id]).idle_status(), (self.players[id]).connected_time_string())
-                    else:
-                        self.groups[k].remove_player(id)
-                        print "Bad Player Ref (#" + id + ") in group"
-                if len(ids) > 0: print ""
-            print "--------------------------------------"
-            print "\nStatistics: groups: " + str(len(self.groups)) + "  players: " +  str(len(self.players))
-        except Exception, e:
-            self.log_msg(str(e))
-        self.p_lock.release()
-
+        #CLEANUP#
+        """
+        display a condensed list of players on the server
+        """
+        with self.p_lock:
+            try:
+                print "------------[ PLAYER LIST ]------------"
+                keys = self.groups.keys()
+                keys.sort(id_compare)
+                for k in keys:
+                    groupstring = "Group " + str(k)  + ": " +  self.groups[k].name
+                    if self.groups[k].pwd != "":
+                        groupstring += " (Pass: \"" + self.groups[k].pwd + "\" )"
+                    print groupstring
+                    ids = self.groups[k].get_player_ids()
+                    ids.sort(id_compare)
+                    for id in ids:
+                        if self.players.has_key(id):
+                            print "  (%s)%s [IP: %s] %s (%s)" % ((self.players[id]).id, (self.players[id]).name, (self.players[id]).ip, (self.players[id]).idle_status(), (self.players[id]).connected_time_string())
+                        else:
+                            self.groups[k].remove_player(id)
+                            print "Bad Player Ref (#" + id + ") in group"
+                    if len(ids) > 0: print ""
+                print "--------------------------------------"
+                print "\nStatistics: groups: " + str(len(self.groups)) + "  players: " +  str(len(self.players))
+            except Exception, e:
+                self.log_msg(str(e))
 
     def player_dump(self):
-        self.p_lock.acquire()
-        try:
-            keys = self.groups.keys()
-            for k in keys:
-                print "Group: %s  %s (pass: \"%s\")" % (str(k),self.groups[k].name, self.groups[k].pwd)
-
-                ids = self.groups[k].get_player_ids()
-                for id in ids:
-                    if self.players.has_key(id):
-                        print str(self.players[id])
-                    else:
-                        self.groups[k].remove_player(id)
-                        print "Bad Player Ref (#" + id + ") in group"
-        except Exception, e:
-            self.log_msg(str(e))
-
-        self.p_lock.release()
-
-
-    def update_request(self,newsock,xml_dom):
+        #CLEANUP
+        with self.p_lock:
+            try:
+                keys = self.groups.keys()
+                for k in keys:
+                    print "Group: %s  %s (pass: \"%s\")" % (str(k),self.groups[k].name, self.groups[k].pwd)
+
+                    ids = self.groups[k].get_player_ids()
+                    for id in ids:
+                        if self.players.has_key(id):
+                            print str(self.players[id])
+                        else:
+                            self.groups[k].remove_player(id)
+                            print "Bad Player Ref (#" + id + ") in group"
+            except Exception, e:
+                self.log_msg(str(e))
+
+    def update_request(self, newsock, etreeEl):
         # handle reconnects
-
-        self.log_msg( "update_request() has been called." )
+        self.log_msg("update_request() has been called.")
 
         # get player id
-        id = xml_dom.getAttribute("id")
-        group_id = xml_dom.getAttribute("group_id")
-
-        self.p_lock.acquire()
-        if self.players.has_key(id):
-            self.sendMsg( newsock, self.players[id].toxml("update"), self.players[id].useCompression, self.players[id].compressionType )
-            self.players[id].reset(newsock)
-            self.players[id].clear_timeout()
-            need_new = 0
-        else:
-            need_new = 1
-        self.p_lock.release()
+        id = etreeEl.get("id")
+        group_id = etreeEl.get("group_id")
+
+        with self.p_lock:
+            if id in self.players:
+                self.sendMsg(newsock, self.players[id].toxml("update"),
+                             self.players[id].useCompression,
+                             self.players[id].compressionType)
+
+                self.players[id].reset(newsock)
+                self.players[id].clear_timeout()
+                need_new = 0
+            else:
+                need_new = 1
 
         if need_new:
             self.new_request(newsock,xml_dom)
         else:
             msg = self.groups[group_id].game_map.get_all_xml()
-            self.send(msg,id,group_id)
-
-
-    def new_request(self,newsock,xml_dom,LOBBY_ID='0'):
+            self.send(msg, id, group_id)
+
+    def new_request(self,newsock, etreeEl, LOBBY_ID='0'):
+        #CLEANUP#
         #build client stub
         props = {}
         # Don't trust what the client tells us...trust what they connected as!
-        props['ip'] = socket.gethostbyname( newsock.getpeername()[0] )
-
-        try:
-            props['role'] = xml_dom.getAttribute("role")
-        except:
-            props['role'] = "GM"
-
-        props['name'] = xml_dom.getAttribute("name")
+        props['ip'] = socket.gethostbyname(newsock.getpeername()[0])
+        props['role'] = etreeEl.get('role', 'GM')
+        props['name'] = etreeEl.get("name")
         props['group_id'] = LOBBY_ID
         props['id'] = str(self.next_player_id)
-        props['version'] = xml_dom.getAttribute("version")
-        props['protocol_version'] = xml_dom.getAttribute("protocol_version")
-        props['client_string'] = xml_dom.getAttribute("client_string")
+        props['version'] = etreeEl.get("version")
+        props['protocol_version'] = etreeEl.get("protocol_version")
+        props['client_string'] = etreeEl.get("client_string")
         self.next_player_id += 1
-        new_stub = client_stub(self.incoming,newsock,props,self.log_console)
-        if xml_dom.hasAttribute('useCompression'):
-            new_stub.useCompression = True
-
-            if xml_dom.hasAttribute('cmpType'):
-                cmpType = xml_dom.getAttribute('cmpType')
-                if cmpBZ2 and cmpType == 'bz2':
-                    new_stub.compressionType = bz2
-                elif cmpZLIB and cmpType == 'zlib':
-                    new_stub.compressionType = zlib
-                else:
-                    new_stub.compressionType = None
-            else:
-                new_stub.compressionType = bz2
-
+        new_stub = client_stub(self.incoming, newsock, props, self.log_console)
+        new_stub.useCompression = bool(etreeEl.get('useCompression'))
+        cmpType = etreeEl.get('cmpType')
+        if cmpBZ2 and cmpType == 'bz2':
+            new_stub.compressionType = bz2
+        elif cmpZLIB and cmpType == 'zlib':
+            new_stub.compressionType = zlib
         else:
-            new_stub.useCompression = False
+            new_stub.compressionType = None
 
         #update newly create client stub with network logging state
         new_stub.EnableMessageLogging = self.log_network_messages
 
         self.sendMsg(newsock, new_stub.toxml("new"), False, None)
 
-        #  try to remove circular refs
-        if xml_dom:
-            xml_dom.unlink()
-
         # send confirmation
-        data = self.recvMsg(newsock, new_stub.useCompression, new_stub.compressionType)
+        data = self.recvMsg(newsock, new_stub.useCompression,
+                            new_stub.compressionType)
         try:
-            xml_dom = parseXml(data)
-            xml_dom = xml_dom._get_documentElement()
-        except Exception, e:
-            print e
-            (remote_host,remote_port) = newsock.getpeername()
-            bad_xml_string =  "Your client sent an illegal message to the server and will be disconnected. "
-            bad_xml_string += "Please report this bug to the development team at:<br /> "
-            bad_xml_string += "<a href=\"http://sourceforge.net/tracker/?group_id=2237&atid=102237\">OpenRPG bugs "
-            bad_xml_string += "(http://sourceforge.net/tracker/?group_id=2237&atid=102237)</a><br />"
-            self.sendMsg( newsock, "<msg to='" + props['id'] + "' from='" + props['id'] + "' group_id='0' />" + bad_xml_string, new_stub.useCompression, new_stub.compressionType)
-
-            time.sleep(2)
+            incEl = messaging.parse(data)
+        except ExpatError:
+            (remote_host, remote_port) = newsock.getpeername()
+            el = messaging.build('msg', "Your client sent an illegal message",
+                                 "to the server and will be disconnected.",
+                                 "Please report this bug to the development",
+                                 "Team at:<br />",
+                                 '<a href="http://sourceforge.net/tracker/?group_id=2237&atid=102237">OpenRPG bugs',
+                                 "(http://sourceforge.net/tracker/?group_id=2237&atid=102237)</a><br />",
+                                 to=props['id'], _from=props['id'],
+                                 group_id=LOBBY_ID)
+            self.sendMsg(newsock, el, new_stub.useCompression,
+                         new_stub.compressionType)
+
+            time.sleep(0.025)
             newsock.close()
-            print "Error in parse found from " + str(remote_host) + ".  Disconnected."
-            print "  Offending data(" + str(len(data)) + "bytes)=" + data
-            print "Exception=" + str(e)
-
-            if xml_dom:
-                xml_dom.unlink()
+            err_msg = ["Error in parse found from ", str(remote_host),
+                       ". Disconnected.\n  Offending data(", str(len(data)),
+                       "bytes)=", data, "\nException=", str(e)]
+            logger.exception(''.join(err_msg))
             return
 
         #start threads and store player
 
-        allowed = 1
+        allowed = True
         version_string = ""
 
         if ((props['protocol_version'] != PROTOCOL_VERSION) and self.validate_protocol):
             version_string = "Sorry, this server can't handle your client version. (Protocol mismatch)<br />"
-            allowed = 0
+            allowed = False
 
         if not self.checkClientVersion(props['version']):
             version_string = "Sorry, your client is out of date. <br />"
             version_string += "This server requires your client be version " + self.minClientVersion + " or higher to connect.<br />"
-            allowed = 0
+            allowed = False
 
         if not allowed:
             version_string += '  Please go to <a href="http://openrpg.digitalxero.net">http://openrpg.digitalxero.net</a> to find a compatible client.<br />'
             version_string += "If you can't find a compatible client on the website, chances are that the server is running an unreleased development version for testing purposes.<br />"
 
-            self.sendMsg( newsock, "<msg to='" + props['id'] + "' from='0' group_id='0' />" + version_string, new_stub.useCompression, new_stub.compressionType)
+            el = messaging.build('msg', version_string, to=props['id'],
+                                         _from='0', group_id='0')
+            self.sendMsg(newsock, el, new_stub.useCompression,
+                          new_stub.compressionType)
             #  Give messages time to flow
             time.sleep(1)
             self.log_msg("Connection terminating due to version incompatibility with client (ver: " + props['version'] + "  protocol: " + props['protocol_version'] + ")" )
             newsock.close()
-            if xml_dom:
-                xml_dom.unlink()
             return None
 
         ip = props['ip']
-        if self.ban_list.has_key(ip):
+        if ip in self.ban_list:
             banmsg = "You have been banned from this server.<br />"
             cmsg = "Banned Client: (" + str(props['id']) + ") " + str(props['name']) + " [" + str(props['ip']) + "]"
             self.log_msg(cmsg)
-            allowed = 0
-
-            self.sendMsg( newsock, "<msg to='" + props['id'] + "' from='0' group_id='0' />" + banmsg, new_stub.useCompression, new_stub.compressionType)
+            allowed = False
+
+            el = messaging.build('msg', banmsg, to=props['id'],
+                                         _from='0', group_id='0')
+            self.sendMsg(newsock, el, new_stub.useCompression,
+                         new_stub.compressionType)
             #  Give messages time to flow
             time.sleep(1)
             newsock.close()
-            if xml_dom:
-                xml_dom.unlink()
             return None
 
         #---- Connection order changed by Snowdog 1/05
@@ -1369,22 +1258,21 @@
         #---- Does not solve the black hole bug totally -SD
 
         try:
-            if xml_dom.getAttribute("id") == props['id']:
+            if incEl.get("id") == props['id']:
                 new_stub.initialize_threads()
-                self.p_lock.acquire()
-                self.players[props['id']] = new_stub
-                self.groups[LOBBY_ID].add_player(props['id']) #always add to lobby on connection.
-                self.send_group_list(props['id'])
-                self.send_player_list(props['id'],LOBBY_ID)
-                self.p_lock.release()
+                with self.p_lock:
+                    self.players[props['id']] = new_stub
+                    self.groups[LOBBY_ID].add_player(props['id']) #always add to lobby on connection.
+                    self.send_group_list(props['id'])
+                    self.send_player_list(props['id'], LOBBY_ID)
 
                 msg = self.groups[LOBBY_ID].game_map.get_all_xml()
-                self.send(msg,props['id'],LOBBY_ID)
-                self.send_to_group(props['id'],LOBBY_ID,self.players[props['id']].toxml('new'))
-                self.return_room_roles(props['id'],LOBBY_ID)
+                self.send(msg, props['id'], LOBBY_ID)
+                self.send_to_group(props['id'], LOBBY_ID, self.players[props['id']].toxml('new'))
+                self.return_room_roles(props['id'], LOBBY_ID)
 
                 # Re-initialize the role for this player incase they came from a different server
-                self.handle_role("set",props['id'], "GM",self.groups[LOBBY_ID].boot_pwd, LOBBY_ID)
+                self.handle_role("set", props['id'], "GM", self.groups[LOBBY_ID].boot_pwd, LOBBY_ID)
 
                 cmsg = "Client Connect: (" + str(props['id']) + ") " + str(props['name']) + " [" + str(props['ip']) + "]"
                 self.log_msg(cmsg)
@@ -1394,7 +1282,7 @@
                 if self.be_registered:
                     self.register()
         except:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
 
             #something didn't go right. Notify client and drop the connection
             err_string = "<center>"
@@ -1402,35 +1290,34 @@
             err_string += "<br /><i>You are being disconnected from the server.</i><br />"
             err_string += "This error may represent a problem with the server. If you continue to get this message "
             err_string += "please contact the servers administrator to correct the issue.</center> "
-            self.sendMsg( newsock, "<msg to='" + props['id'] + "' from='" + props['id'] + "' group_id='0' />" + err_string, new_stub.useCompression, new_stub.compressionType )
+
+            el = messaging.build('msg', err_string, to=props['id'],
+                                 _from=props['id'],group_id='0')
+            self.sendMsg(newsock, el, new_stub.useCompression,
+                         new_stub.compressionType )
             time.sleep(2)
             newsock.close()
-
+            return
 
         #  Display the lobby message
-        self.SendLobbyMessage(newsock,props['id'])
-
-        if xml_dom:
-            xml_dom.unlink()
+        self.SendLobbyMessage(newsock, props['id'])
 
 
     def checkClientVersion(self, clientversion):
         minv = self.minClientVersion.split('.')
         cver = clientversion.split('.')
         for i in xrange(min(len(minv),len(cver))):
-            w=max(len(minv[i]),len(cver[i]))
-            v1=minv[i].rjust(w);
-            v2=cver[i].rjust(w);
-            if v1<v2:
-                return 1
-            if v1>v2:
-                return 0
-
-        if len(minv)>len(cver):
-            return 0
-        return 1
-
-
+            w = max(len(minv[i]),len(cver[i]))
+            v1 = minv[i].rjust(w);
+            v2 = cver[i].rjust(w);
+            if v1 < v2:
+                return True
+            if v1 > v2:
+                return False
+
+        if len(minv) > len(cver):
+            return False
+        return True
 
     def SendLobbyMessage(self, socket, player_id):
         #######################################################################
@@ -1455,18 +1342,26 @@
                                      "default_LobbyMessage.html")
             except:
                 pass
+
             else:
-                open_msg = open(self.userPath + "LobbyMessage.html", "r")
-                lobbyMsg += open_msg.read()
-                open_msg.close()
+                with open(self.userPath + "LobbyMessage.html", "r") as f:
+                    lobbyMsg += f.read()
+
+            el = messaging.build('msg', lobbyMsg, to=player_id, _from='0',
+                                 group_id='0')
 
             # Send the server's lobby message to the client no matter what
-            self.sendMsg(socket, "<msg to='" + player_id + "' from='0' group_id='0' />" + lobbyMsg, self.players[player_id].useCompression, self.players[player_id].compressionType)
+            self.sendMsg(socket, el, self.players[player_id].useCompression,
+                         self.players[player_id].compressionType)
             if self.sendLobbySound:
-                self.sendMsg(socket, '<sound url="' + self.lobbySound + '" group_id="0" from="0" loop="True" />', self.players[player_id].useCompression, self.players[player_id].compressionType)
+                el = messaging.build('sound', url=self.lobbySound, loop=True,
+                                     group_id=0, _from=0)
+                self.sendMsg(socket, el,
+                             self.players[player_id].useCompression,
+                             self.players[player_id].compressionType)
             return
         except:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
         #  End of lobby message code
         #######################################################################
 
@@ -1480,14 +1375,11 @@
                 adder = self.server_address
             self.listen_sock.bind(('', self.server_port))
             self.listen_sock.listen(5)
-
         except Exception, e:
             self.log_msg(("Error binding request socket!", e))
             self.alive = 0
 
-
         while self.alive:
-
             #  Block on the socket waiting for a new connection
             try:
                 (newsock, addr) = self.listen_sock.accept()
@@ -1498,10 +1390,8 @@
                 # our face!  :O  After words, this thread is dead ready for another connection
                 # accept to come in.
                 thread.start_new_thread(self.acceptedNewConnectionThread, ( newsock, addr ))
-
             except:
-                print "The following exception caught accepting new connection:"
-                traceback.print_exc()
+                logger.exception(traceback.format_exc())
 
         #  At this point, we're done and cleaning up.
         self.log_msg("server socket listening thread exiting...")
@@ -1509,25 +1399,25 @@
 
 
 
-    def acceptedNewConnectionThread( self, newsock, addr ):
-        """Once a new connection comes in and is accepted, this thread starts up to handle it."""
-
-        # Initialize xml_dom
-        xml_dom = None
+    def acceptedNewConnectionThread(self, newsock, addr):
+        """
+        Once a new connection comes in and is accepted,
+        this thread starts up to handle it.
+        """
         data = None
 
         # get client info and send othe client info
         # If this receive fails, this thread should exit without even attempting to process it
         self.log_msg("Connection from " + str(addr) + " has been accepted.  Waiting for data...")
 
-        data = self.recvMsg( newsock )
-
-        if data=="" or data == None:
+        data = self.recvMsg(newsock)
+
+        if not data:
             self.log_msg("Connection from " + str(addr) + " failed. Closing connection.")
             try:
                 newsock.close()
             except Exception, e:
-                self.log_msg( str(e) )
+                self.log_msg(str(e))
                 print str(e)
             return #returning causes connection thread instance to terminate
 
@@ -1539,68 +1429,37 @@
                 pass
             return #returning causes connection thread instance to terminate
 
-        #  Clear out the xml_dom in preparation for new stuff, if necessary
+        # Parse the XML received from the connecting client
         try:
-            if xml_dom:
-                xml_dom.unlink()
-
-        except:
-            self.log_msg( "The following exception caught unlinking xml_dom:")
-            self.log_msg("Continuing")
-
-            try:
-                newsock.close()
-            except:
-                pass
-            return #returning causes connection thread instance to terminate
-
-        #  Parse the XML received from the connecting client
-        try:
-            xml_dom = parseXml(data)
-            xml_dom = xml_dom._get_documentElement()
-
-        except:
+            el = fromstring(data)
+        except ExpatError:
             try:
                 newsock.close()
             except:
                 pass
-            self.log_msg( "Error in parse found from " + str(addr) + ".  Disconnected.")
+            self.log_msg("Error in parse found from " + str(addr) + ".  Disconnected.")
             self.log_msg("  Offending data(" + str(len(data)) + "bytes)=" + data)
-            self.log_msg( "Exception:")
-            traceback.print_exc()
-            return #returning causes connection thread instance to terminate
-
-        #  Determine the correct action and execute it
+            logger.exception(traceback.format_exc())
+            return
+
+        # Determine the correct action and execute it
         try:
             # get action
-            action = xml_dom.getAttribute("action")
+            action = el.get("action")
 
             # Figure out what type of connection we have going on now
             if action == "new":
-                self.new_request(newsock,xml_dom)
-
+                self.new_request(newsock, el)
             elif action == "update":
-                self.update_request(newsock,xml_dom)
-
+                self.update_request(newsock, el)
             else:
                 self.log_msg("Unknown Join Request!")
 
         except Exception, e:
-            print "The following  message: " + str(data)
-            print "from " + str(addr) + " created the following exception: "
-            traceback.print_exc()
-            return #returning causes connection thread instance to terminate
-
-        #  Again attempt to clean out DOM stuff
-        try:
-            if xml_dom:
-                xml_dom.unlink()
-        except:
-            print "The following exception caught unlinking xml_dom:"
-            traceback.print_exc()
-            return #returning causes connection thread instance to terminate
-
-
+            logger.exception(traceback.format_exc())
+            logger.general("The following  message: " + str(data))
+            logger.general("from " + str(addr) + " cause the above exception")
+            return
 
     #========================================================
     #
@@ -1611,16 +1470,14 @@
     # Changed thread organization from one continuous parsing/handling thread
     # to multiple expiring parsing/handling threads to improve server performance
     # and player load capacity -- Snowdog 3/04
-
-    def message_handler(self,arg):
-        xml_dom = None
-        self.log_msg( "message handler thread running..." )
+    def message_handler(self, arg):
+        self.log_msg("message handler thread running...")
         while self.alive:
             data = None
             try:
-                data=self.incoming.get(0)
+                data = self.incoming.get(0)
             except Queue.Empty:
-                time.sleep(0.5) #sleep 1/2 second
+                time.sleep(0.025)
                 continue
 
             bytes = len(data)
@@ -1634,118 +1491,112 @@
                 data = None
             except Exception, e:
                 self.log_msg(str(e))
-                if xml_dom: xml_dom.unlink()
-        if xml_dom: xml_dom.unlink()
         self.log_msg("message handler thread exiting...")
         self.incoming_event.set()
 
-    def parse_incoming_dom(self,data):
-        end = data.find(">") #locate end of first element of message
-        head = data[:end+1]
-        #self.log_msg(head)
-        xml_dom = None
+
+
+    def parse_incoming_dom(self, data):
         try:
-            xml_dom = parseXml(head)
-            xml_dom = xml_dom._get_documentElement()
-            self.message_action(xml_dom,data)
-
-        except Exception, e:
-            print "Error in parse of inbound message. Ignoring message."
-            print "  Offending data(" + str(len(data)) + "bytes)=" + data
-            print "Exception=" + str(e)
-
-        if xml_dom: xml_dom.unlink()
-
-
-    def message_action(self, xml_dom, data):
-        tag_name = xml_dom._get_tagName()
-        if self.svrcmds.has_key(tag_name):
-            self.svrcmds[tag_name]['function'](xml_dom,data)
+            el = fromstring(data)
+        except ExpatError:
+            end = data.find(">")
+            head = data[:end+1]
+            msg = data[end+1:]
+            el = fromstring(head)
+            try:
+                el1 = fromstring(msg)
+                el.append(el1)
+            except ExpatError:
+                el.text = msg
+                logger.general("Bad Message: \n" + data)
+
+        self.message_action(el, data)
+
+    def message_action(self, etreeEL, data):
+        if etreeEL.tag in self.svrcmds:
+            self.svrcmds[etreeEL.tag]['function'](etreeEL, data)
         else:
             raise Exception, "Not a valid header!"
         #Message Action thread expires and closes here.
         return
 
 
-    def do_alter(self, xml_dom, data):
-        target = xml_dom.getAttribute("key")
-        value = xml_dom.getAttribute("val")
-        player = xml_dom.getAttribute("plr")
-        group_id = xml_dom.getAttribute("gid")
-        boot_pwd = xml_dom.getAttribute("bpw")
+    def do_alter(self, etreeEL, data):
+        target = etreeEL.get("key")
+        value = etreeEL.get("val")
+        player = etreeEL.get("plr")
+        group_id = etreeEL.get("gid")
+        boot_pwd = etreeEL.get("bpw")
         actual_boot_pwd = self.groups[group_id].boot_pwd
 
-        if self.allow_room_passwords == 0:
-            msg ="<msg to='" + player + "' from='0' group_id='0' /> Room passwords have been disabled by the server administrator."
-            self.players[player].outbox.put(msg)
+        if not self.allow_room_passwords:
+            el = messaging.build('msg', "Room passwords have been disabled",
+                                 "by the server administrator.", to=player,
+                                 _from=0, group_id=0)
+            self.players[player].outbox.put(el)
             return
         elif boot_pwd == actual_boot_pwd:
             if target == "pwd":
                 lmessage = "Room password changed to from \"" + self.groups[group_id].pwd + "\" to \"" + value  + "\" by " + player
                 self.groups[group_id].pwd = value
-                msg ="<msg to='" + player + "' from='0' group_id='0' /> Room password changed to \"" +  value + "\"."
-                self.players[player].outbox.put(msg)
+                el = messaging.build('msg', 'Room password changed to', value,
+                                     to=player, _from=0, group_id=0)
+                self.players[player].outbox.put(el)
                 self.log_msg(lmessage)
-                self.send_to_all('0',self.groups[group_id].toxml('update'))
+                self.send_to_all('0', self.groups[group_id].toxml('update'))
             elif target == "name":
-                # Check for & in name.  We want to allow this because of its common
-                # use in d&d games
-                result = self.change_group_name(group_id,value,player)
-                msg ="<msg to='" + player + "' from='0' group_id='0' />" + result
-                self.players[player].outbox.put(msg)
+                el = messaging.build('msg', self.change_group_name(group_id,
+                                                                   value,
+                                                                   player),
+                                     to=player, _from=0, group_id=0)
+                self.players[player].outbox.put(el)
         else:
-            msg ="<msg to='" + player + "' from='0' group_id='0'>Invalid Administrator Password."
-            self.players[player].outbox.put(msg)
-
-
-    def do_role(self, xml_dom, data):
+            el = messaging.build('msg', "Invalid Administrator Password.",
+                                 to=player, _from=0, group_id=0)
+            self.players[player].outbox.put(el)
+
+
+    def do_role(self, etreeEL, data):
         role = ""
         boot_pwd = ""
-        act = xml_dom.getAttribute("action")
-        player = xml_dom.getAttribute("player")
-        group_id = xml_dom.getAttribute("group_id")
+        act = etreeEL.get("action")
+        player = etreeEL.get("player")
+        group_id = etreeEL.get("group_id")
         if act == "set":
-            role = xml_dom.getAttribute("role")
-            boot_pwd = xml_dom.getAttribute("boot_pwd")
-        xml_dom.unlink()
+            role = etreeEL.get("role")
+            boot_pwd = etreeEL.get("boot_pwd")
+
         if group_id != "0":
             self.handle_role(act, player, role, boot_pwd, group_id)
             self.log_msg(("role", (player, role)))
 
-    def do_ping(self, xml_dom, data):
-        player = xml_dom.getAttribute("player")
-        group_id = xml_dom.getAttribute("group_id")
-        sent_time = ""
-        msg = ""
-        try:
-            sent_time = xml_dom.getAttribute("time")
-        except:
-            pass
-
-        if sent_time != "":
+    def do_ping(self, xetreeEL, data):
+        player = etreeEL.get("player")
+        group_id = etreeEL.get("group_id")
+        sent_time = etreeEL.get("time")
+
+        if sent_time:
             #because a time was sent return a ping response
-            msg ="<ping time='" + str(sent_time) + "' />"
+            el = messaging.build('ping', time=sent_time)
         else:
-            msg ="<msg to='" + player + "' from='" + player + "' group_id='" + group_id + "'><font color='#FF0000'>PONG!?!</font>"
-
-        self.players[player].outbox.put(msg)
-        xml_dom.unlink()
+            el = messaging.build('msg', '<font color="#FF0000">PONG!?!</font>',
+                                 to=player, _from=player, group_id=group_id)
+
+        self.players[player].outbox.put(el)
 
     def do_system(self, xml_dom, data):
         pass
 
-    def moderate_group(self,xml_dom,data):
+    def moderate_group(self, etreeEl, data):
         try:
-            action = xml_dom.getAttribute("action")
-            from_id = xml_dom.getAttribute("from")
-            if xml_dom.hasAttribute("pwd"):
-                pwd=xml_dom.getAttribute("pwd")
-            else:
-                pwd=""
-            group_id=self.players[from_id].group_id
+            action = etreeEl.get("action")
+            from_id = etreeEl.get("from")
+            pwd = etreeEl.get("pwd", '')
+            group_id = self.players[from_id].group_id
 
             if action == "list":
-                if (self.groups[group_id].moderated):
+                if self.groups[group_id].moderated:
                     msg = ""
                     for i in self.groups[group_id].voice.keys():
                         if msg != "":
@@ -1777,43 +1628,40 @@
                 if not self.groups[group_id].check_boot_pwd(pwd):
                     self.players[from_id].self_message("Failed - incorrect admin password")
                     return
-                users = xml_dom.getAttribute("users").split(',')
+                users = etreeEl.get("users",'').split(',')
                 for i in users:
                     self.groups[group_id].voice[i.strip()]=1
             elif action == "delvoice":
                 if not self.groups[group_id].check_boot_pwd(pwd):
                     self.players[from_id].self_message("Failed - incorrect admin password")
                     return
-                users = xml_dom.getAttribute("users").split(',')
+                users = etreeEl.get("users",'').split(',')
                 for i in users:
                     if self.groups[group_id].voice.has_key(i.strip()):
                         del self.groups[group_id].voice[i.strip()]
             else:
                 print "Bad input: " + data
-
         except Exception,e:
             self.log_msg(str(e))
 
-
-
-
-    def join_group(self,xml_dom,data):
+    def join_group(self, etreeEl, data):
         try:
-            from_id = xml_dom.getAttribute("from")
-            pwd = xml_dom.getAttribute("pwd")
-            group_id = xml_dom.getAttribute("group_id")
+            from_id = etreeEl.get("from")
+            pwd = etreeEl.get("pwd")
+            group_id = etreeEl.get("group_id")
             ver = self.players[from_id].version
-            allowed = 1
+            allowed = True
 
             if not self.groups[group_id].check_version(ver):
-                allowed = 0
+                allowed = False
                 msg = 'failed - invalid client version ('+self.groups[group_id].minVersion+' or later required)'
 
             if not self.groups[group_id].check_pwd(pwd):
-                allowed = 0
+                allowed = False
 
                 #tell the clients password manager the password failed -- SD 8/03
-                pm = "<password signal=\"fail\" type=\"room\" id=\"" +  group_id  + "\" data=\"\"/>"
+                pm = messaging.build('password', signal='fail', type='room',
+                                     id=group_id, data='')
                 self.players[from_id].outbox.put(pm)
 
                 msg = 'failed - incorrect room password'
@@ -1823,8 +1671,9 @@
                 #the following line makes sure that their role is reset to normal,
                 #since it is briefly set to lurker when they even TRY to change
                 #rooms
-                msg = "<role action=\"update\" id=\"" + from_id  + "\" role=\"" + self.players[from_id].role + "\" />"
-                self.players[from_id].outbox.put(msg)
+                el = messaging.build('role', action='update', id=from_id,
+                                     role=self.players[from_id].role)
+                self.players[from_id].outbox.put(el)
                 return
 
             #move the player into their new group.
@@ -1833,18 +1682,10 @@
         except Exception, e:
             self.log_msg(str(e))
 
-
-
-
-    #----------------------------------------------------------------------------
-    # move_player function -- added by Snowdog 4/03
-    #
-    # Split join_group function in half. separating the player validation checks
-    # from the actual group changing code. Done primarily to impliment
-    # boot-from-room-to-lobby behavior in the server.
-
     def move_player(self, from_id, group_id ):
-        "move a player from one group to another"
+        """
+        move a player from one group to another
+        """
         try:
             try:
                 if group_id == "0":
@@ -1852,8 +1693,7 @@
                 else:
                     self.players[from_id].role = "Lurker"
             except Exception, e:
-                print "exception in move_player() "
-                traceback.print_exc()
+                logger.exception(traceback.format_exc())
 
             old_group_id = self.players[from_id].change_group(group_id,self.groups)
             self.send_to_group(from_id,old_group_id,self.players[from_id].toxml('del'))
@@ -1870,217 +1710,176 @@
                 try:
                     if self.groups[group_id].messageFile[:4] == 'http':
                         data = urllib.urlretrieve(self.groups[group_id].messageFile)
-                        roomMsgFile = open(data[0])
+                        with open(data[0]) as f:
+                            roomMsg = f.read()
+
+                        urllib.urlcleanup()
                     else:
-                        roomMsgFile = open(self.groups[group_id].messageFile, "r")
-                    roomMsg = roomMsgFile.read()
-                    roomMsgFile.close()
-                    urllib.urlcleanup()
+                        with open(self.groups[group_id].messageFile, "r") as f:
+                            roomMsg = f.read()
 
                 except Exception, e:
                     roomMsg = ""
                     self.log_msg(str(e))
 
                 # Spit that darn message out now!
-                self.players[from_id].outbox.put("<msg to='" + from_id + "' from='0' group_id='" + group_id + "' />" + roomMsg)
+                el = messaging.build('msg', roomMsg, to=from_id, _from=0,
+                                     group_id=group_id)
+                self.players[from_id].outbox.put(el)
 
             if self.sendLobbySound and group_id == '0':
-                self.players[from_id].outbox.put('<sound url="' + self.lobbySound + '" group_id="0" from="0" loop="True" />')
+                el = messaging.build('sound', url=self.lobbySound, loop=True,
+                                     _from=0, group_id=0)
+                self.players[from_id].outbox.put(el)
 
             # Now, tell everyone that we've arrived
             self.send_to_all('0', self.groups[group_id].toxml('update'))
 
             # this line sends a handle role message to change the players role
-            self.send_player_list(from_id,group_id)
+            self.send_player_list(from_id, group_id)
 
             #notify user about others in the room
-            self.return_room_roles(from_id,group_id)
+            self.return_room_roles(from_id, group_id)
             self.log_msg(("join_group", (from_id, group_id)))
-            self.handle_role("set", from_id, self.players[from_id].role, self.groups[group_id].boot_pwd, group_id)
+            self.handle_role("set", from_id, self.players[from_id].role,
+                             self.groups[group_id].boot_pwd, group_id)
 
         except Exception, e:
             self.log_msg(str(e))
 
-        thread.start_new_thread(self.registerRooms,(0,))
-
-    def return_room_roles(self,from_id,group_id):
+        thread.start_new_thread(self.registerRooms, (0,))
+
+    def return_room_roles(self, from_id, group_id):
         for m in self.players.keys():
             if self.players[m].group_id == group_id:
-                msg = "<role action=\"update\" id=\"" + self.players[m].id  + "\" role=\"" + self.players[m].role + "\" />"
-                self.players[from_id].outbox.put(msg)
-
+                el = messaging.build('role', action='update',
+                                     id=self.players[m].id,
+                                     role=self.players[m].role)
+                self.players[from_id].outbox.put(el)
 
     # This is pretty much the same thing as the create_group method, however,
     # it's much more generic whereas the create_group method is tied to a specific
     # xml message.  Ack!  This version simply creates the groups, it does not
     # send them to players.  Also note, both these methods have race
     # conditions written all over them.  Ack! Ack!
-    def new_group( self, name, pwd, boot, minVersion, mapFile, messageFile, persist = 0, moderated=0 ):
-        group_id = str( self.next_group_id )
+    def new_group(self, name, pwd, boot, minVersion, mapFile, messageFile,
+                   persist=False, moderated=False):
+        group_id = str(self.next_group_id)
         self.next_group_id += 1
 
-        self.groups[group_id] = game_group( group_id, name, pwd, "", boot, minVersion, mapFile, messageFile, persist )
+        self.groups[group_id] = game_group(group_id, name, pwd, "", boot,
+                                           minVersion, mapFile, messageFile,
+                                           persist)
         self.groups[group_id].moderated = moderated
         ins = ""
-        if persist !=0: ins="Persistant "
+        if persist:
+            ins = "Persistant "
         lmsg = "Creating " + ins + "Group... (" + str(group_id) + ") " + str(name)
-        self.log_msg( lmsg )
-
-
-    def change_group_name(self,gid,name,pid):
-        "Change the name of a group"
+        self.log_msg(lmsg)
+
+
+    def change_group_name(self, gid, name, pid):
+        """
+        Change the name of a group
+        """
         # Check for & in name.  We want to allow this because of its common
         # use in d&d games.
+        name = name.replace('&', '&amp;').replace('"', '&quote;').replace("'",
+                                                                        '&#39;')
         try:
-            loc = name.find("&")
-            oldloc = 0
-            while loc > -1:
-                loc = name.find("&",oldloc)
-                if loc > -1:
-                    b = name[:loc]
-                    e = name[loc+1:]
-                    value = b + "&amp;" + e
-                    oldloc = loc+1
-
-            loc = name.find("'")
-            oldloc = 0
-            while loc > -1:
-                loc = name.find("'",oldloc)
-                if loc > -1:
-                    b = name[:loc]
-                    e = name[loc+1:]
-                    name = b + "&#39;" + e
-                    oldloc = loc+1
-
-            loc = name.find('"')
-            oldloc = 0
-            while loc > -1:
-                loc = name.find('"',oldloc)
-                if loc > -1:
-                    b = name[:loc]
-                    e = name[loc+1:]
-                    name = b + "&quot;" + e
-                    oldloc = loc+1
-
             oldroomname = self.groups[gid].name
             self.groups[gid].name = str(name)
             lmessage = "Room name changed to from \"" + oldroomname + "\" to \"" + name + "\""
             self.log_msg(lmessage  + " by " + str(pid) )
-            self.send_to_all('0',self.groups[gid].toxml('update'))
+            self.send_to_all('0', self.groups[gid].toxml('update'))
             return lmessage
         except:
             return "An error occured during rename of room!"
 
-        thread.start_new_thread(self.registerRooms,(0,))
-
-
-
-    def create_group(self,xml_dom,data):
+        thread.start_new_thread(self.registerRooms, (0,))
+
+    def create_group(self, etreeEl, data):
         try:
-            from_id = xml_dom.getAttribute("from")
-            pwd = xml_dom.getAttribute("pwd")
-            name = xml_dom.getAttribute("name")
-            boot_pwd = xml_dom.getAttribute("boot_pwd")
-            minVersion = xml_dom.getAttribute("min_version")
+            from_id = etreeEl.get("from")
+            pwd = etreeEl.get("pwd")
+            name = etreeEl.get("name")
+            boot_pwd = etreeEl.get("boot_pwd")
+            minVersion = etreeEl.get("min_version")
             #added var reassign -- akoman
             messageFile = self.defaultMessageFile
 
             # see if passwords are allowed on this server and null password if not
-            if self.allow_room_passwords != 1: pwd = ""
-
+            if not self.allow_room_passwords:
+                pwd = ""
 
             #
             # Check for & in name.  We want to allow this because of its common
             # use in d&d games.
-
-            loc = name.find("&")
-            oldloc = 0
-            while loc > -1:
-                loc = name.find("&",oldloc)
-                if loc > -1:
-                    b = name[:loc]
-                    e = name[loc+1:]
-                    name = b + "&amp;" + e
-                    oldloc = loc+1
-
-            loc = name.find("'")
-            oldloc = 0
-            while loc > -1:
-                loc = name.find("'",oldloc)
-                if loc > -1:
-                    b = name[:loc]
-                    e = name[loc+1:]
-                    name = b + "&#39;" + e
-                    oldloc = loc+1
-
-            loc = name.find('"')
-            oldloc = 0
-            while loc > -1:
-                loc = name.find('"',oldloc)
-                if loc > -1:
-                    b = name[:loc]
-                    e = name[loc+1:]
-                    name = b + "&quot;" + e
-                    oldloc = loc+1
-
+            name = name.replace('&', '&amp;').replace('"', '&quote;').\
+                 replace("'", '&#39;')
 
             group_id = str(self.next_group_id)
             self.next_group_id += 1
-            self.groups[group_id] = game_group(group_id,name,pwd,"",boot_pwd, minVersion, None, messageFile )
-            self.groups[group_id].voice[from_id]=1
+            self.groups[group_id] = game_group(group_id, name, pwd, "",
+                                               boot_pwd, minVersion, None,
+                                               messageFile)
+            self.groups[group_id].voice[from_id] = True
             self.players[from_id].outbox.put(self.groups[group_id].toxml('new'))
             old_group_id = self.players[from_id].change_group(group_id,self.groups)
-            self.send_to_group(from_id,old_group_id,self.players[from_id].toxml('del'))
+            self.send_to_group(from_id, old_group_id,
+                               self.players[from_id].toxml('del'))
             self.check_group(from_id, old_group_id)
-            self.send_to_all(from_id,self.groups[group_id].toxml('new'))
-            self.send_to_all('0',self.groups[group_id].toxml('update'))
-            self.handle_role("set",from_id,"GM",boot_pwd, group_id)
+            self.send_to_all(from_id, self.groups[group_id].toxml('new'))
+            self.send_to_all('0', self.groups[group_id].toxml('update'))
+            self.handle_role("set", from_id, "GM", boot_pwd, group_id)
             lmsg = "Creating Group... (" + str(group_id) + ") " + str(name)
-            self.log_msg( lmsg )
+            self.log_msg(lmsg)
             jmsg = "moving to room " + str(group_id) + "."
-            self.log_msg( jmsg )
+            self.log_msg(jmsg)
             #even creators of the room should see the HTML --akoman
             #edit: jan10/03 - was placed in the except statement. Silly me.
             if self.defaultMessageFile != None:
                 if self.defaultMessageFile[:4] == 'http':
                     data = urllib.urlretrieve(self.defaultMessageFile)
-                    open_msg = open(data[0])
+                    with open(data[0]) as f:
+                        roomMsg = f.read()
                     urllib.urlcleanup()
                 else:
-                    open_msg = open( self.defaultMessageFile, "r" )
-
-                roomMsg = open_msg.read()
-                open_msg.close()
+                    with open(self.defaultMessageFile, "r") as f:
+                        roomMsg = f.read()
+
                 # Send the rooms message to the client no matter what
-                self.players[from_id].outbox.put( "<msg to='" + from_id + "' from='0' group_id='" + group_id + "' />" + roomMsg )
+                el = messaging.build('msg', roomMsg, to=from_id, _from=0,
+                                     group_id=group_id)
+                self.players[from_id].outbox.put(el)
 
         except Exception, e:
             self.log_msg( "Exception: create_group(): " + str(e))
 
-        thread.start_new_thread(self.registerRooms,(0,))
+        thread.start_new_thread(self.registerRooms, (0,))
 
 
     def check_group(self, from_id, group_id):
         try:
             if group_id not in self.groups: return
             if group_id == '0':
-                self.send_to_all("0",self.groups[group_id].toxml('update'))
+                self.send_to_all("0", self.groups[group_id].toxml('update'))
                 return #never remove lobby *sanity check*
             if not self.isPersistentRoom(group_id)  and self.groups[group_id].get_num_players() == 0:
-                self.send_to_all("0",self.groups[group_id].toxml('del'))
+                self.send_to_all("0", self.groups[group_id].toxml('del'))
                 del self.groups[group_id]
                 self.log_msg(("delete_group", (from_id, group_id)))
 
             else:
-                self.send_to_all("0",self.groups[group_id].toxml('update'))
+                self.send_to_all("0", self.groups[group_id].toxml('update'))
 
             #The register Rooms thread
-            thread.start_new_thread(self.registerRooms,(0,))
+            thread.start_new_thread(self.registerRooms, (0,))
 
         except Exception, e:
             self.log_msg(str(e))
 
-    def del_player(self,id,group_id):
+    def del_player(self, id, group_id):
         try:
             dmsg = "Client Disconnect: (" + str(id) + ") " + str(self.players[id].name)
             self.players[id].disconnect()
@@ -2088,58 +1887,48 @@
             del self.players[id]
             self.log_msg(dmsg)
 
-
             #  If already registered then re-register, thereby updating the Meta
             #    on the number of players
             #  Note:  Upon server shutdown, the server is first unregistered, so
             #           this code won't be repeated for each player being deleted.
             if self.be_registered:
                 self.register()
-
-
         except Exception, e:
             self.log_msg(str(e))
 
-        self.log_msg("Explicit garbage collection shows %s undeletable items." % str(gc.collect()))
-
-
-
-    def incoming_player_handler(self,xml_dom,data):
-        id = xml_dom.getAttribute("id")
-        act = xml_dom.getAttribute("action")
-        #group_id = xml_dom.getAttribute("group_id")
+    def incoming_player_handler(self, etreeEl, data):
+        id = etreeEl.get("id")
+        act = etreeEl.get("action")
         group_id = self.players[id].group_id
         ip = self.players[id].ip
         self.log_msg("Player with IP: " + str(ip) + " joined.")
 
         ServerPlugins.setPlayer(self.players[id])
 
-        self.send_to_group(id,group_id,data)
-        if act=="new":
+        self.send_to_group(id, group_id, data)
+        if act == "new":
             try:
-                self.send_player_list(id,group_id)
+                self.send_player_list(id, group_id)
                 self.send_group_list(id)
             except Exception, e:
-                traceback.print_exc()
-        elif act=="del":
-            #print "del player"
-            self.del_player(id,group_id)
+                logger.exception(traceback.format_exc())
+        elif act == "del":
+            self.del_player(id, group_id)
             self.check_group(id, group_id)
-        elif act=="update":
-            self.players[id].take_dom(xml_dom)
+        elif act == "update":
+            self.players[id].take_dom(etreeEl)
             self.log_msg(("update", {"id": id,
-                                     "name": xml_dom.getAttribute("name"),
-                                     "status": xml_dom.getAttribute("status"),
-                                     "role": xml_dom.getAttribute("role"),
+                                     "name": etreeEl.get("name"),
+                                     "status": etreeEl.get("status"),
+                                     "role": etreeEl.get("role"),
                                      "ip":  str(ip),
-                                     "group": xml_dom.getAttribute("group_id"),
-                                     "room": xml_dom.getAttribute("name"),
-                                     "boot": xml_dom.getAttribute("rm_boot"),
-                                     "version": xml_dom.getAttribute("version"),
-                                     "ping": xml_dom.getAttribute("time") \
+                                     "group": etreeEl.get("group_id"),
+                                     "room": etreeEl.get("name"),
+                                     "boot": etreeEl.get("rm_boot"),
+                                     "version": etreeEl.get("version"),
+                                     "ping": etreeEl.get("time") \
                                      }))
 
-
     def strip_cheat_roll(self, string):
         try:
             cheat_regex = re.compile('&amp;#91;(.*?)&amp;#93;')
@@ -2161,12 +1950,12 @@
             return True
         return False
 
-    def incoming_msg_handler(self,xml_dom,data):
-        xml_dom, data = ServerPlugins.preParseIncoming(xml_dom, data)
-
-        to_id = xml_dom.getAttribute("to")
-        from_id = xml_dom.getAttribute("from")
-        group_id = xml_dom.getAttribute("group_id")
+    def incoming_msg_handler(self, etreeEl, data):
+        etreeEl, data = ServerPlugins.preParseIncoming(etreeEl, data)
+
+        to_id = etreeEl.get("to")
+        from_id = etreeEl.get("from")
+        group_id = etreeEl.get("group_id")
         end = data.find(">")
         msg = data[end+1:]
 
@@ -2174,226 +1963,216 @@
             print "WARNING!! Message received with an invalid from_id.  Message dropped."
             return None
 
-        #
-        # check for < body to prevent someone from changing the background
-        #
-
-        data = self.strip_body_tags(data)
-
-        #
-        # check for &#91 and &#93  codes which are often used to cheat with dice.
-        #
-        if self.players[from_id].role != "GM":
-            data = self.strip_cheat_roll(data)
-
-        if group_id == '0' and self.msgTooLong(len(msg) and msg[:5] == '<chat'):
+        if etreeEl.text:
+            etreeEl.text = self.strip_body_tags(etreeEl.text)
+
+        if self.players[from_id].role != "GM" and etreeEl.iter_find('chat'):
+            el = etreeEl.find('chat')
+            el.text = self.strip_cheat_roll(el.text)
+
+        if group_id == '0' and etreeEl.iter_find('chat') and self.msgTooLong(len(etreeEl.iter_find('chat').text)):
             self.send("Your message was too long, break it up into smaller parts please", from_id, group_id)
             self.log_msg('Message Blocked from Player: ' + self.players[from_id].name + ' attempting to send a message longer then ' + str(self.maxSendSize))
             return
 
-        if msg[:4] == '<map':
+        if etreeEl.iter_find('map'):
             if group_id == '0':
                 #attempt to change lobby map. Illegal operation.
                 self.players[from_id].self_message('The lobby map may not be altered.')
             elif to_id.lower() == 'all':
                 #valid map for all players that is not the lobby.
-                self.send_to_group(from_id,group_id,data)
-                self.groups[group_id].game_map.init_from_xml(msg)
+                self.send_to_group(from_id, group_id, etreeEl)
+                self.groups[group_id].game_map.init_from_xml(etreeEl.iter_find('map'))
             else:
                 #attempting to send map to specific individuals which is not supported.
                 self.players[from_id].self_message('Invalid map message. Message not sent to others.')
 
-        elif msg[:6] == '<boot ':
-            self.handle_boot(from_id,to_id,group_id,msg)
-
+        elif etreeEl.iter_find('boot'):
+            self.handle_boot(from_id, to_id, group_id, etreeEl.iter_find('boot'))
         else:
             if to_id == 'all':
-                if self.groups[group_id].moderated and not self.groups[group_id].voice.has_key(from_id):
+                if self.groups[group_id].moderated and\
+                   not from_id in self.groups[group_id].voice:
                     self.players[from_id].self_message('This room is moderated - message not sent to others')
                 else:
-                    self.send_to_group(from_id,group_id,data)
+                    self.send_to_group(from_id, group_id, etreeEl)
             else:
-                self.players[to_id].outbox.put(data)
+                self.players[to_id].outbox.put(etreeEl)
 
         self.check_group_members(group_id)
         return
 
-    def sound_msg_handler(self, xml_dom, data):
-        from_id = xml_dom.getAttribute("from")
-        group_id = xml_dom.getAttribute("group_id")
+    def sound_msg_handler(self, etreeEl, data):
+        from_id = etreeEl.get("from")
+        group_id = etreeEl.get("group_id")
         if group_id != 0:
-            self.send_to_group(from_id, group_id, data)
-
-    def plugin_msg_handler(self,xml_dom,data):
-        to_id = xml_dom.getAttribute("to")
-        from_id = xml_dom.getAttribute("from")
-        group_id = xml_dom.getAttribute("group_id")
-        end = data.find(">")
-        msg = data[end+1:]
+            self.send_to_group(from_id, group_id, etreeEl)
+
+    def plugin_msg_handler(self, etreeEl, data):
+        to_id = etreeEl.get("to")
+        from_id = etreeEl.get("from")
+        group_id = etreeEl.get("group_id")
 
         if from_id == "0" or len(from_id) == 0:
             print "WARNING!! Message received with an invalid from_id.  Message dropped."
             return None
 
-
         if to_id == 'all':
             if self.groups[group_id].moderated and not self.groups[group_id].voice.has_key(from_id):
                 self.players[from_id].self_message('This room is moderated - message not sent to others')
             else:
-                self.send_to_group(from_id, group_id, msg)
+                for child in etreeEl.getchildren():
+                    self.send_to_group(from_id, group_id, child)
         else:
-            self.players[to_id].outbox.put(msg)
+            for child in etreeEl.getchildren():
+                self.players[to_id].outbox.put(child)
 
         self.check_group_members(group_id)
         return
 
     def handle_role(self, act, player, role, given_boot_pwd, group_id):
         if act == "display":
-            msg = "<msg to=\"" + player + "\" from=\"0\" group_id=\"" + group_id + "\" />"
-            msg += "Displaying Roles<br /><br /><u>Role</u>&nbsp&nbsp&nbsp<u>Player</u><br />"
+            msg = ["Displaying Roles<br /><br /><u>Role</u>",
+                   "&nbsp&nbsp<u>Player</u><br />"]
             keys = self.players.keys()
             for m in keys:
                 if self.players[m].group_id == group_id:
-                    msg += self.players[m].role + " " + self.players[m].name + "<br />"
-            self.send(msg,player,group_id)
+                    msg.append(self.players[m].role)
+                    msg.append(self.players[m].name)
+                    msg.append("<br />")
+            el = messaging.build('msg', ' '.join(msg), to=player, _from=0,
+                                 group_id=group_id)
+            self.send(el, player, group_id)
         elif act == "set":
             try:
                 actual_boot_pwd = self.groups[group_id].boot_pwd
                 if self.players[player].group_id == group_id:
                     if actual_boot_pwd == given_boot_pwd:
-                        self.log_msg( "Administrator passwords match -- changing role")
+                        self.log_msg("Administrator passwords match -- changing role")
 
                         #  Send update role event to all
-                        msg = "<role action=\"update\" id=\"" + player  + "\" role=\"" + role + "\" />"
-                        self.send_to_group("0", group_id, msg)
+                        el = messaging.build('role', action='update', id=player,
+                                             role=role)
+                        self.send_to_group("0", group_id, el)
                         self.players[player].role = role
                         if (role.lower() == "gm" or role.lower() == "player"):
                             self.groups[group_id].voice[player]=1
                     else:
-                        #tell the clients password manager the password failed -- SD 8/03
-                        pm = "<password signal=\"fail\" type=\"admin\" id=\"" + group_id + "\" data=\"\"/>"
-                        self.players[player].outbox.put(pm)
+                        el = messaging.build('password', signal='fail',
+                                             type='admin', id=group_id, data='')
+                        self.players[player].outbox.put(el)
                         self.log_msg( "Administrator passwords did not match")
             except Exception, e:
                 print e
                 print "Error executing the role change"
                 print "due to the following exception:"
-                traceback.print_exc()
+                logger.exception(traceback.format_exc())
                 print "Ignoring boot message"
 
-    def handle_boot(self,from_id,to_id,group_id,msg):
-        xml_dom = None
+    def handle_boot(self, from_id, to_id, group_id, etreeEl):
+        given_boot_pwd = etreeEl.get('boot_pwd')
+
         try:
-            given_boot_pwd = None
-            try:
-                xml_dom = parseXml(msg)
-                xml_dom = xml_dom._get_documentElement()
-                given_boot_pwd = xml_dom.getAttribute("boot_pwd")
-
-            except:
-                print "Error in parse of boot message, Ignoring."
-                print "Exception: "
-                traceback.print_exc()
-
-            try:
-                actual_boot_pwd = self.groups[group_id].boot_pwd
-                server_admin_pwd = self.groups["0"].boot_pwd
-
-                self.log_msg("Actual boot pwd = " + actual_boot_pwd)
-                self.log_msg("Given boot pwd = " + given_boot_pwd)
-
-                if self.players[to_id].group_id == group_id:
-
-                    ### ---CHANGES BY SNOWDOG 4/03 ---
-                    ### added boot to lobby code.
-                    ### if boot comes from lobby dump player from the server
-                    ### any user in-room boot will dump to lobby instead
-                    if given_boot_pwd == server_admin_pwd:
-                        # Send a message to everyone in the room, letting them know someone has been booted
-                        boot_msg = "<msg to='all' from='%s' group_id='%s'/><font color='#FF0000'>Booting '(%s) %s' from server...</font>" % (from_id, group_id, to_id, self.players[to_id].name)
-
-                        self.log_msg("boot_msg:" + boot_msg)
-
-                        self.send_to_group( "0", group_id, boot_msg )
-                        time.sleep( 1 )
-
-                        self.log_msg("Booting player " + str(to_id) + " from server.")
-
-                        #  Send delete player event to all
-                        self.send_to_group("0",group_id,self.players[to_id].toxml("del"))
-
-                        #  Remove the player from local data structures
-                        self.del_player(to_id,group_id)
-
-                        #  Refresh the group data
-                        self.check_group(to_id, group_id)
-
-                    elif actual_boot_pwd == given_boot_pwd:
-                        # Send a message to everyone in the room, letting them know someone has been booted
-                        boot_msg = "<msg to='all' from='%s' group_id='%s'/><font color='#FF0000'>Booting '(%s) %s' from room...</font>" % (from_id, group_id, to_id, self.players[to_id].name)
-
-                        self.log_msg("boot_msg:" + boot_msg)
-
-                        self.send_to_group( "0", group_id, boot_msg )
-                        time.sleep( 1 )
-
-                        #dump player into the lobby
-                        self.move_player(to_id,"0")
-
-                        #  Refresh the group data
-                        self.check_group(to_id, group_id)
-                    else:
-                        #tell the clients password manager the password failed -- SD 8/03
-                        pm = "<password signal=\"fail\" type=\"admin\" id=\"" + group_id + "\" data=\"\"/>"
-                        self.players[from_id].outbox.put(pm)
-                        print "boot passwords did not match"
-
-            except Exception, e:
-                traceback.print_exc()
-                self.log_msg('Exception in handle_boot() ' + str(e))
-
-        finally:
-            try:
-                if xml_dom:
-                    xml_dom.unlink()
-            except Exception, e:
-                traceback.print_exc()
-                self.log_msg('Exception in xml_dom.unlink() ' + str(e))
-
-
-    #---------------------------------------------------------------
-    # admin_kick function -- by Snowdog 4/03
-    # 9/17/05 updated to allow stealth boots (no client chat announce) -SD
-    #---------------------------------------------------------------
-    def admin_kick(self, id, message="", silent = 0 ):
-        "Kick a player from a server from the console"
-
+            actual_boot_pwd = self.groups[group_id].boot_pwd
+            server_admin_pwd = self.groups["0"].boot_pwd
+
+            self.log_msg("Actual boot pwd = " + actual_boot_pwd)
+            self.log_msg("Given boot pwd = " + given_boot_pwd)
+
+            if self.players[to_id].group_id == group_id:
+
+                ### ---CHANGES BY SNOWDOG 4/03 ---
+                ### added boot to lobby code.
+                ### if boot comes from lobby dump player from the server
+                ### any user in-room boot will dump to lobby instead
+                if given_boot_pwd == server_admin_pwd:
+                    # Send a message to everyone in the room, letting them know someone has been booted
+                    msg = ['<font color="#FF0000">Booting', '(%s)' % to_id,
+                           self.players[to_id].name,
+                           'from server...</font>']
+                    el = messaging.build('msg', ' '.join(msg), to='all',
+                                         _from=from_id, group_id=group_id)
+
+                    self.log_msg("boot_msg:" + ' '.join(msg))
+
+                    self.send_to_group("0", group_id, el)
+                    time.sleep(0.025)
+
+                    self.log_msg("Booting player " + str(to_id) + " from server.")
+
+                    #  Send delete player event to all
+                    self.send_to_group("0", group_id, self.players[to_id].toxml("del"))
+
+                    #  Remove the player from local data structures
+                    self.del_player(to_id, group_id)
+
+                    #  Refresh the group data
+                    self.check_group(to_id, group_id)
+
+                elif actual_boot_pwd == given_boot_pwd:
+                    # Send a message to everyone in the room, letting them know someone has been booted
+                    msg = ['<font color="#FF0000">Booting', '(%s)' % to_id,
+                           self.players[to_id].name,
+                           'from room...</font>']
+                    el = messaging.build('msg', ' '.join(msg), to='all',
+                                         _from=from_id, group_id=group_id)
+
+                    self.log_msg("boot_msg:" + ' '.join(msg))
+
+                    self.send_to_group("0", group_id, el)
+                    time.sleep(0.025)
+
+                    #dump player into the lobby
+                    self.move_player(to_id, "0")
+
+                    #  Refresh the group data
+                    self.check_group(to_id, group_id)
+                else:
+                    #tell the clients password manager the password failed -- SD 8/03
+                    el = messaging.build('password', signal='fail', data='',
+                                         id=group_id, type='admin')
+                    self.players[from_id].outbox.put(el)
+                    print "boot passwords did not match"
+
+        except Exception, e:
+            logger.exception(traceback.format_exc())
+            self.log_msg('Exception in handle_boot() ' + str(e))
+
+    def admin_kick(self, id, message="", silent=0):
+        """
+        Kick a player from a server from the console
+        """
         try:
             group_id = self.players[id].group_id
             # Send a message to everyone in the victim's room, letting them know someone has been booted
-            boot_msg = "<msg to='all' from='0' group_id='%s'/><font color='#FF0000'>Kicking '(%s) %s' from server... %s</font>" % ( group_id, id, self.players[id].name, str(message))
-            self.log_msg("boot_msg:" + boot_msg)
+            msg = ['<font color="#FF0000">Kicking', '(%s)' % (id),
+                   self.players[id].name, 'from server...', str(message)]
+            el = messaging.build('msg', ' '.join(msg), to='all', _from=0,
+                                 group_id=group_id)
+            self.log_msg("boot_msg:" + ' '.join(msg))
             if (silent == 0):
-                self.send_to_group( "0", group_id, boot_msg )
-            time.sleep( 1 )
+                self.send_to_group("0", group_id, el)
+            time.sleep(0.025)
 
             self.log_msg("kicking player " + str(id) + " from server.")
             #  Send delete player event to all
-            self.send_to_group("0",group_id,self.players[id].toxml("del"))
+            self.send_to_group("0", group_id, self.players[id].toxml("del"))
 
             #  Remove the player from local data structures
-            self.del_player(id,group_id)
+            self.del_player(id, group_id)
 
             #  Refresh the group data
             self.check_group(id, group_id)
 
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg('Exception in admin_kick() ' + str(e))
 
 
-    def admin_banip(self, ip, name="", silent = 0):
-        "Ban a player from a server from the console"
+    def admin_banip(self, ip, name="", silent=0):
+        """
+        Ban a player from a server from the console
+        """
         try:
             self.ban_list[ip] = {}
             self.ban_list[ip]['ip'] = ip
@@ -2401,10 +2180,10 @@
             self.saveBanList()
 
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg('Exception in admin_banip() ' + str(e))
 
-    def admin_ban(self, id, message="", silent = 0):
+    def admin_ban(self, id, message="", silent=0):
         "Ban a player from a server from the console"
         try:
             id = str(id)
@@ -2416,11 +2195,14 @@
             self.saveBanList()
 
             # Send a message to everyone in the victim's room, letting them know someone has been booted
-            ban_msg = "<msg to='all' from='0' group_id='%s'/><font color='#FF0000'>Banning '(%s) %s' from server... %s</font>" % ( group_id, id, self.players[id].name, str(message))
-            self.log_msg("ban_msg:" + ban_msg)
+            msg = ['<font color="#FF0000">Banning', '(%s)' % (id),
+                   self.players[id].name, 'from server...', str(message)]
+            el = messaging.build('msg', ' '.join(msg), to='all', _from=0,
+                                 group_id=group_id)
+            self.log_msg("ban_msg:" + ' '.join(msg))
             if (silent == 0):
-                self.send_to_group("0", group_id, ban_msg)
-            time.sleep( .1 )
+                self.send_to_group("0", group_id, el)
+            time.sleep(0.025)
 
             self.log_msg("baning player " + str(id) + " from server.")
             #  Send delete player event to all
@@ -2433,18 +2215,18 @@
             self.check_group(id, group_id)
 
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg('Exception in admin_ban() ' + str(e))
 
     def admin_unban(self, ip):
         try:
-            if self.ban_list.has_key(ip):
+            if ip in self.ban_list:
                 del self.ban_list[ip]
 
             self.saveBanList()
 
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg('Exception in admin_unban() ' + str(e))
 
     def admin_banlist(self):
@@ -2461,22 +2243,21 @@
         return "".join(msg)
 
     def admin_toggleSound(self):
-        if self.sendLobbySound:
-            self.sendLobbySound = False
-        else:
-            self.sendLobbySound = True
+        self.sendLobbySound = not self.sendLobbySound
 
         return self.sendLobbySound
 
-    def admin_soundFile(self, file):
-        self.lobbySound = file
+    def admin_soundFile(self, _file):
+        self.lobbySound = _file
 
     def admin_setSendSize(self, sendlen):
-        self.maxSendSize = sendlen
+        self.maxSendSize = int(sendlen)
         self.log_msg('Max Send Size was set to ' + str(sendlen))
 
     def remove_room(self, group):
-        "removes a group and boots all occupants"
+        """
+        removes a group and boots all occupants
+        """
         #check that group id exists
         if group not in self.groups:
             return "Invalid Room Id. Ignoring remove request."
@@ -2490,36 +2271,31 @@
         except:
             pass
 
-    def send(self,msg,player,group):
+    def send(self, msg, player, group):
         self.players[player].send(msg,player,group)
 
-
-    def send_to_all(self,from_id,data):
+    def send_to_all(self, from_id, etreeEl):
         try:
-            self.p_lock.acquire()
-            keys = self.players.keys()
-            self.p_lock.release()
-            for k in keys:
-                if k != from_id:
-                    self.players[k].outbox.put(data)
+            with self.p_lock:
+                for player in self.players.itervalues():
+                    if player.id != from_id:
+                        player.outbox.put(etreeEl)
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg("Exception: send_to_all(): " + str(e))
 
-
-
-    def send_to_group(self, from_id, group_id, data):
-        data = ServerPlugins.postParseIncoming(data)
+    def send_to_group(self, from_id, group_id, etreeEl):
+        etreeEl = ServerPlugins.postParseIncoming(etreeEl)
         try:
             keys = self.groups[group_id].get_player_ids()
             for k in keys:
                 if k != from_id:
-                    self.players[k].outbox.put(data)
+                    self.players[k].outbox.put(etreeEl)
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg("Exception: send_to_group(): " + str(e))
 
-    def send_player_list(self,to_id,group_id):
+    def send_player_list(self, to_id, group_id):
         try:
             keys = self.groups[group_id].get_player_ids()
             for k in keys:
@@ -2527,7 +2303,7 @@
                     data = self.players[k].toxml('new')
                     self.players[to_id].outbox.put(data)
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg("Exception: send_player_list(): " + str(e))
 
     def send_group_list(self, to_id, action="new"):
@@ -2537,7 +2313,7 @@
                 self.players[to_id].outbox.put(xml)
         except Exception, e:
             self.log_msg("Exception: send_group_list(): (client #"+to_id+") : " + str(e))
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
 
     #--------------------------------------------------------------------------
     # KICK_ALL_CLIENTS()
@@ -2551,9 +2327,9 @@
             for k in keys:
                 pl = self.groups[k].get_player_ids()
                 for p in pl:
-                    self.admin_kick(p,"Purged from server")
+                    self.admin_kick(p, "Purged from server")
         except Exception, e:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             self.log_msg("Exception: kick_all_clients(): " + str(e))
 
 
@@ -2573,16 +2349,18 @@
                 idlemins = self.players[k].idle_time()
                 idlemins = idlemins/60
                 if (idlemins > self.zombie_time):
-                    self.admin_kick(k,"Removing zombie client", self.silent_auto_kick)
+                    self.admin_kick(k, "Removing zombie client",
+                                    self.silent_auto_kick)
                 elif self.players[k].get_status() != MPLAY_CONNECTED:
                     if self.players[k].check_time_out():
                         self.log_msg("Player #" + k + " Lost connection!")
-                        self.admin_kick(k,"Removing dead client", self.silent_auto_kick)
+                        self.admin_kick(k, "Removing dead client",
+                                        self.silent_auto_kick)
         except Exception, e:
             self.log_msg("Exception: check_group_members(): " + str(e))
 
 
-    def remote_admin_handler(self,xml_dom,data):
+    def remote_admin_handler(self, etreeEl, data):
         # handle incoming remove server admin messages
         # (allows basic administration of server from a remote client)
         # base message format: <admin id="" pwd="" cmd="" [data for command]>
@@ -2591,10 +2369,10 @@
             return
 
         try:
-            pid = xml_dom.getAttribute("id")
+            pid = etreeEl.get("id")
             gid = ""
-            given_pwd = xml_dom.getAttribute("pwd")
-            cmd = xml_dom.getAttribute("cmd")
+            given_pwd = etreeEl.get("pwd")
+            cmd = etreeEl.get("cmd")
             server_admin_pwd = self.groups["0"].boot_pwd
             p_id = ""
             p_name= ""
@@ -2602,28 +2380,29 @@
 
 
             #verify that the message came from the proper ID/Socket and get IP address for logging
-            if self.players.has_key(pid):
-                p_name=(self.players[pid]).name
-                p_ip=(self.players[pid]).ip
-                gid=(self.players[pid]).group_id
+            if pid in self.players:
+                p_name = (self.players[pid]).name
+                p_ip = (self.players[pid]).ip
+                gid = (self.players[pid]).group_id
             else:
                 #invalid ID.. report fraud and log
                 m = "Invalid Remote Server Control Message (invalid id) #" + str(pid) + " does not exist."
-                self.log_msg( m )
+                self.log_msg(m)
                 return
 
             #log receipt of admin command   added by Darren
             m = "Remote Server Control Message ( "+ str(cmd) +" ) from #" + str(pid) + " (" + str(p_name) + ") " + str(p_ip)
-            self.log_msg ( m )
+            self.log_msg(m)
 
             #check the admin password(boot password) against the supplied one in message
             #dump and log any attempts to control server remotely with invalid password
             if server_admin_pwd != given_pwd:
                 #tell the clients password manager the password failed -- SD 8/03
-                pm = "<password signal=\"fail\" type=\"server\" id=\"" + str(self.players[pid].group_id) + "\" data=\"\"/>"
+                pm = messaging.build('password', signal='fail', type='server',
+                                     id=gid, data='')
                 self.players[pid].outbox.put(pm)
                 m = "Invalid Remote Server Control Message (bad password) from #" + str(pid) + " (" + str(p_name) + ") " + str(p_ip)
-                self.log_msg( m )
+                self.log_msg(m)
                 return
 
             #message now deemed 'authentic'
@@ -2631,52 +2410,65 @@
 
             if cmd == "list":
                 #return player list to this user.
-                msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + self.player_list_remote()
+                msg = messaging.build('msg', self.player_list_remote(), to=pid,
+                                      _from=0, group_id=gid)
                 self.players[pid].outbox.put(msg)
 
             elif cmd == "banip":
-                ip = xml_dom.getAttribute("bip")
-                name = xml_dom.getAttribute("bname")
-                msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'> Banned: " + str(ip)
+                ip = etreeEl.get("bip")
+                name = etreeEl.get("bname")
+                msg = messaging.build('msg', 'Banned:', ip, to=pid, _from=0,
+                                      group_id=gid)
                 self.admin_banip(ip, name)
+                self.players[pid].outbox.put(msg)
 
             elif cmd == "ban":
-                id = xml_dom.getAttribute("bid")
-                msg = "<msg to='" + id + "' from='0' group_id='" + gid + "'> Banned!"
+                id = etreeEl.get("bid")
+                msg = messaging.build('msg', 'Banned!', to=id, _from=0,
+                                      group_id=gid)
                 self.players[pid].outbox.put(msg)
                 self.admin_ban(id, "")
 
             elif cmd == "unban":
-                ip = xml_dom.getAttribute("ip")
+                ip = etreeEl.get("ip")
                 self.admin_unban(ip)
-                msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'> Unbaned: " + str(ip)
+                msg = messaging.build('msg', 'Unbanned:', ip, to=pid, _from=0,
+                                      group_id=gid)
                 self.players[pid].outbox.put(msg)
 
             elif cmd == "banlist":
-                msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + self.admin_banlist()
+                msg = messaging.build('msg', self.admin_banlist(), to=pid,
+                                      _from=0, group_id=gid)
                 self.players[pid].outbox.put(msg)
 
             elif cmd == "killgroup":
-                ugid = xml_dom.getAttribute("gid")
+                ugid = etreeEl.get("gid")
                 if ugid == "0":
-                    m = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>Cannot Remove Lobby! Remote administrator request denied!"
+                    msg = messaging.build('msg', 'Cannot Remove Lobby! Remote',
+                                          'administrator request denied!',
+                                          to=pid, _from=0, group_id=gid)
                     self.players[pid].outbox.put(m)
                 else:
                     result = self.prune_room(ugid)
-                    msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + str(result)
+                    msg = messaging.build('msg', str(result), to=pid, _from=0,
+                                          group_id=gid)
                     self.players[pid].outbox.put(msg)
 
             elif cmd == "message":
-                tuid = xml_dom.getAttribute("to_id")
-                msg = xml_dom.getAttribute("msg")
-                pmsg = "<msg to='" + tuid + "' from='0' group_id='" + self.players[tuid].group_id + "' >" + msg
-                try: self.players[tuid].outbox.put(pmsg)
+                tuid = etreeEl.get("to_id")
+                msg = etreeEl.get("msg")
+                pmsg = messaging.build('msg', msg, to=tuid, _from=0,
+                                       group_id=self.players[tuid].group_id)
+                try:
+                    self.players[tuid].outbox.put(pmsg)
                 except:
-                    msg = "<msg to='" + pid + "' from='0' group_id='" + gid + ">Unknown Player ID: No message sent."
+                    msg = messaging.build('msg', 'Unknown Player ID:',
+                                          'No message sent.', to=pid, _from=0,
+                                          group_id=gid)
                     self.players[pid].outbox.put(msg)
 
             elif cmd == "broadcast":
-                bmsg = xml_dom.getAttribute("msg")
+                bmsg = etreeEl.get("msg")
                 self.broadcast(bmsg)
 
             elif cmd == "killserver" and self.allowRemoteKill:
@@ -2684,45 +2476,54 @@
                 self.kill_server()
 
             elif cmd == "uptime":
-                msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + self.uptime(1)
+                msg = messaging.build('msg', self.uptime(1), to=pid, _from=0,
+                                      group_id=gid)
                 self.players[pid].outbox.put(msg)
 
             elif cmd == "help":
-                msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>"
-                msg += self.AdminHelpMessage()
-                self.players[pid].outbox.put( msg)
+                msg = messaging.build('msg', self.AdminHelpMessage(), to=pid,
+                                      _from=0, group_id=gid)
+                self.players[pid].outbox.put(msg)
 
             elif cmd == "roompasswords":
                 # Toggle if room passwords are allowed on this server
-                msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>"
-                msg += self.RoomPasswords()
+                msg = messaging.build('msg', self.RoomPasswords(), to=pid,
+                                      _from=0, group_id=gid)
                 self.players[pid].outbox.put( msg)
 
             elif cmd == "createroom":
                 rm_name = xml_dom.getAttribute("name")
                 rm_pass = xml_dom.getAttribute("pass")
                 rm_boot = xml_dom.getAttribute("boot")
-                result = self.create_temporary_persistant_room(rm_name, rm_boot, rm_pass)
-                msg = "<msg to='" + pid + "' from='0' group_id='" + gid + "'>" + result
+                result = self.create_temporary_persistant_room(rm_name,
+                                                               rm_boot, rm_pass)
+
+                msg = messaging.build('msg', result, to=pid, _from=0,
+                                      group_id=gid)
                 self.players[pid].outbox.put(msg)
 
             elif cmd == "nameroom":
                 rm_id   = xml_dom.getAttribute("rmid")
                 rm_name = xml_dom.getAttribute("name")
                 result = self.change_group_name(rm_id,rm_name,pid)
-                msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'/>" + result
+
+                msg = messaging.build('msg', result, to=pid, _from=0, group_id=gid)
                 self.players[pid].outbox.put(msg)
 
             elif cmd == "passwd":
                 tgid = xml_dom.getAttribute("gid")
                 npwd = xml_dom.getAttribute("pass")
                 if tgid == "0":
-                    msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>Server password may not be changed remotely!"
+                    msg = messaging.build('msg', 'Server password may not'
+                                          'be changed remotely!', to=pid,
+                                          _from=0, group_id=gid)
                     self.players[pid].outbox.put(msg)
                 else:
                     try:
                         self.groups[tgid].boot_pwd = npwd
-                        msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>Password changed for room " + tgid
+                        msg = messaging.build('msg', 'Password changed for room',
+                                              tgid, to=pid, _from=0,
+                                              group_id=gid)
                         self.players[pid].outbox.put(msg)
                     except: pass
 
@@ -2730,33 +2531,29 @@
                 for g in self.groups.itervalues():
                     g.save_map()
 
-                msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'>Persistent room maps saved"
+                msg = messaging.build('msg', 'Persistent room maps saved', to=pid, _from=0, group_id=gid)
                 self.players[pid].outbox.put(msg)
 
 
             else:
-                msg ="<msg to='" + pid + "' from='0' group_id='" + gid + "'><i>[Unknown Remote Administration Command]</i>"
+                msg = messaging.build('msg', '<i>[Unknown Remote'
+                                      'Administration Command]</i>', to=pid,
+                                      _from=0, group_id=gid)
                 self.players[pid].outbox.put(msg)
 
 
         except Exception, e:
             self.log_msg("Exception: Remote Admin Handler Error: " + str(e))
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
 
 
     def toggleRemoteKill(self):
-        if self.allowRemoteKill:
-            self.allowRemoteKill = False
-        else:
-            self.allowRemoteKill = True
+        self.allowRemoteKill = not self.allowRemoteKill
 
         return self.allowRemoteKill
 
     def toggleRemoteAdmin(self):
-        if self.allowRemoteAdmin:
-            self.allowRemoteAdmin = False
-        else:
-            self.allowRemoteAdmin = True
+        self.allowRemoteAdmin = not self.allowRemoteAdmin
 
         return self.allowRemoteAdmin
 
@@ -2764,7 +2561,9 @@
 # Remote Administrator Help (returns from server not client)
 #-----------------------------------------------------------------
     def AdminHelpMessage(self):
-        "returns a string to be sent as a message to a remote admin"
+        """
+        returns a string to be sent as a message to a remote admin
+        """
 
         #define the help command list information
         info = []
@@ -2798,7 +2597,6 @@
         help += "</table>"
         return help
 
-
     #----------------------------------------------------------------
     # Create Persistant Group -- Added by Snowdog 6/03
     #
@@ -2809,11 +2607,10 @@
     # Updated function code to use per-group based persistance and
     # removed references to outdated persistRoomIdThreshold
     #----------------------------------------------------------------
-
     def create_temporary_persistant_room(self, roomname, bootpass, password=""):
-        # if the room id just above the persistant room limit is available (not in use)
-            # then it will be assigned as a persistant room on the server
-        "create a temporary persistant room"
+        """
+        create a temporary persistant room
+        """
 
         group_id = str(self.next_group_id)
         self.next_group_id += 1
@@ -2833,10 +2630,10 @@
     # Added some error checking and updated room removal for per-room
     # based persistance -- Snowdog 4/04
     #----------------------------------------------------------------
-
     def prune_room(self,group):
         #don't allow lobby to be removed
-        if group == '0': return "Lobby is required to exist and cannot be removed."
+        if group == '0':
+            return "Lobby is required to exist and cannot be removed."
 
         #check that group id exists
         if group not in self.groups:
@@ -2848,14 +2645,16 @@
                 self.move_player(k,'0')
 
             ins = "Room"
-            if self.isPersistentRoom(group) : ins="Persistant room"
-            self.send_to_all("0",self.groups[group].toxml('del'))
+            if self.isPersistentRoom(group):
+                ins = "Persistant room"
+
+            self.send_to_all("0", self.groups[group].toxml('del'))
             del self.groups[group]
             self.log_msg(("delete_group", ('0',group)))
             return ins + " removed."
 
         except:
-            traceback.print_exc()
+            logger.exception(traceback.format_exc())
             return "An Error occured on the server during room removal!"
 
 
@@ -2866,6 +2665,9 @@
 #  in chat window on remote client
 #----------------------------------------------------------------
     def player_list_remote(self):
+        """
+        display a condensed list of players on the server
+        """
         COLOR1 = "\"#004080\""  #header/footer background color
         COLOR2 = "\"#DDDDDD\""  #group line background color
         COLOR3 = "\"#FFFFFF\""  #player line background color
@@ -2876,43 +2678,41 @@
         SIZE   = "size=\"-1\""  #player info text size
         FG = PCOLOR
 
-
-        "display a condensed list of players on the server"
-        self.p_lock.acquire()
-        pl = "<br /><table border=\"0\" cellpadding=\"1\" cellspacing=\"2\">"
-        pl += "<tr><td colspan='4' bgcolor=" + COLOR1 + "><font color=" + COLOR4 + "><b>GROUP &amp; PLAYER LIST</b></font></td></tr>"
-        try:
-
-            keys = self.groups.keys()
-            keys.sort(id_compare)
-            for k in keys:
-                groupstring = "<tr><td bgcolor=" + COLOR2 + " colspan='2'><b>Group " + str(k)  + ": " +  self.groups[k].name  + "</b>"
-                groupstring += "</td><td bgcolor=" + COLOR2 + " > <i>Password: \"" + self.groups[k].pwd + "\"</td><td bgcolor=" + COLOR2 + " > Boot: \"" + self.groups[k].boot_pwd + "\"</i></td></tr>"
-                pl += groupstring
-                ids = self.groups[k].get_player_ids()
-                ids.sort(id_compare)
-                for id in ids:
-                    if self.players.has_key(id):
-                        if k != "0":
-                            if (self.players[id]).role == "GM": FG = GCOLOR
-                            elif (self.players[id]).role == "Player": FG = PCOLOR
-                            else: FG = LCOLOR
-                        else: FG = PCOLOR
-                        pl += "<tr><td bgcolor=" + COLOR3 + ">"
-                        pl += "<font color=" + FG + " " + SIZE + ">&nbsp;&nbsp;(" +  (self.players[id]).id  + ") "
-                        pl += (self.players[id]).name
-                        pl += "</font></td><td bgcolor=" + COLOR3 + " ><font color=" + FG + " " + SIZE + ">[IP: " + (self.players[id]).ip + "]</font></td><td  bgcolor=" + COLOR3 + " ><font color=" + FG + " " + SIZE + "> "
-                        pl += (self.players[id]).idle_status()
-                        pl += "</font></td><td><font color=" + FG + " " + SIZE + ">"
-                        pl += (self.players[id]).connected_time_string()
-                        pl += "</font>"
-
-                    else:
-                        self.groups[k].remove_player(id)
-                        pl +="<tr><td colspan='4' bgcolor=" + COLOR3 + " >Bad Player Ref (#" + id + ") in group"
-                pl+="</td></tr>"
-            pl += "<tr><td colspan='4' bgcolor=" + COLOR1 + "><font color=" + COLOR4 + "><b><i>Statistics: groups: " + str(len(self.groups)) + "  players: " +  str(len(self.players)) + "</i></b></font></td></tr></table>"
-        except Exception, e:
-            self.log_msg(str(e))
-        self.p_lock.release()
+        with self.p_lock:
+            pl = "<br /><table border=\"0\" cellpadding=\"1\" cellspacing=\"2\">"
+            pl += "<tr><td colspan='4' bgcolor=" + COLOR1 + "><font color=" + COLOR4 + "><b>GROUP &amp; PLAYER LIST</b></font></td></tr>"
+            try:
+
+                keys = self.groups.keys()
+                keys.sort(id_compare)
+                for k in keys:
+                    groupstring = "<tr><td bgcolor=" + COLOR2 + " colspan='2'><b>Group " + str(k)  + ": " +  self.groups[k].name  + "</b>"
+                    groupstring += "</td><td bgcolor=" + COLOR2 + " > <i>Password: \"" + self.groups[k].pwd + "\"</td><td bgcolor=" + COLOR2 + " > Boot: \"" + self.groups[k].boot_pwd + "\"</i></td></tr>"
+                    pl += groupstring
+                    ids = self.groups[k].get_player_ids()
+                    ids.sort(id_compare)
+                    for id in ids:
+                        if self.players.has_key(id):
+                            if k != "0":
+                                if (self.players[id]).role == "GM": FG = GCOLOR
+                                elif (self.players[id]).role == "Player": FG = PCOLOR
+                                else: FG = LCOLOR
+                            else: FG = PCOLOR
+                            pl += "<tr><td bgcolor=" + COLOR3 + ">"
+                            pl += "<font color=" + FG + " " + SIZE + ">&nbsp;&nbsp;(" +  (self.players[id]).id  + ") "
+                            pl += (self.players[id]).name
+                            pl += "</font></td><td bgcolor=" + COLOR3 + " ><font color=" + FG + " " + SIZE + ">[IP: " + (self.players[id]).ip + "]</font></td><td  bgcolor=" + COLOR3 + " ><font color=" + FG + " " + SIZE + "> "
+                            pl += (self.players[id]).idle_status()
+                            pl += "</font></td><td><font color=" + FG + " " + SIZE + ">"
+                            pl += (self.players[id]).connected_time_string()
+                            pl += "</font>"
+
+                        else:
+                            self.groups[k].remove_player(id)
+                            pl +="<tr><td colspan='4' bgcolor=" + COLOR3 + " >Bad Player Ref (#" + id + ") in group"
+                    pl+="</td></tr>"
+                pl += "<tr><td colspan='4' bgcolor=" + COLOR1 + "><font color=" + COLOR4 + "><b><i>Statistics: groups: " + str(len(self.groups)) + "  players: " +  str(len(self.players)) + "</i></b></font></td></tr></table>"
+            except Exception, e:
+                self.log_msg(str(e))
+
         return pl