]> de.git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/views/submission.py
Should be ready to go now!
[xonotic/xonstat.git] / xonstat / views / submission.py
index 4e54a5e7fa8202e7e6c2b73e83af0b9668359157..7f011fbfa4df988a937e1050d3d7acd1c1b70ecc 100755 (executable)
@@ -1,16 +1,50 @@
 import datetime\r
 import logging\r
 import datetime\r
 import logging\r
+import os\r
+import pyramid.httpexceptions\r
 import re\r
 import time\r
 import re\r
 import time\r
-from pyramid.config import get_current_registry\r
 from pyramid.response import Response\r
 from pyramid.response import Response\r
+from sqlalchemy import Sequence\r
 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound\r
 from xonstat.d0_blind_id import d0_blind_id_verify\r
 from xonstat.models import *\r
 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound\r
 from xonstat.d0_blind_id import d0_blind_id_verify\r
 from xonstat.models import *\r
-from xonstat.util import strip_colors\r
+from xonstat.util import strip_colors, qfont_decode\r
 \r
 log = logging.getLogger(__name__)\r
 \r
 \r
 log = logging.getLogger(__name__)\r
 \r
+\r
+def is_blank_game(players):\r
+    """Determine if this is a blank game or not. A blank game is either:\r
+\r
+    1) a match that ended in the warmup stage, where accuracy events are not\r
+    present\r
+\r
+    2) a match in which no player made a positive or negative score AND was\r
+    on the scoreboard\r
+    """\r
+    r = re.compile(r'acc-.*-cnt-fired')\r
+    flg_nonzero_score = False\r
+    flg_acc_events = False\r
+\r
+    for events in players:\r
+        if is_real_player(events):\r
+            for (key,value) in events.items():\r
+                if key == 'scoreboard-score' and value != '0':\r
+                    flg_nonzero_score = True\r
+                if r.search(key):\r
+                    flg_acc_events = True\r
+\r
+    return not (flg_nonzero_score and flg_acc_events)\r
+\r
+def get_remote_addr(request):\r
+    """Get the Xonotic server's IP address"""\r
+    if 'X-Forwarded-For' in request.headers:\r
+        return request.headers['X-Forwarded-For']\r
+    else:\r
+        return request.remote_addr\r
+\r
+\r
 def is_supported_gametype(gametype):\r
     """Whether a gametype is supported or not"""\r
     flg_supported = True\r
 def is_supported_gametype(gametype):\r
     """Whether a gametype is supported or not"""\r
     flg_supported = True\r
@@ -22,17 +56,35 @@ def is_supported_gametype(gametype):
 \r
 \r
 def verify_request(request):\r
 \r
 \r
 def verify_request(request):\r
-    (idfp, status) = d0_blind_id_verify(\r
-            sig=request.headers['X-D0-Blind-Id-Detached-Signature'],\r
-            querystring='',\r
-            postdata=request.body)\r
+    try:\r
+        (idfp, status) = d0_blind_id_verify(\r
+                sig=request.headers['X-D0-Blind-Id-Detached-Signature'],\r
+                querystring='',\r
+                postdata=request.body)\r
 \r
 \r
-    log.debug('\nidfp: {0}\nstatus: {1}'.format(idfp, status))\r
+        log.debug('\nidfp: {0}\nstatus: {1}'.format(idfp, status))\r
+    except: \r
+        idfp = None\r
+        status = None\r
 \r
     return (idfp, status)\r
 \r
 \r
 \r
     return (idfp, status)\r
 \r
 \r
-def has_minimum_real_players(player_events):\r
+def num_real_players(player_events, count_bots=False):\r
+    """\r
+    Returns the number of real players (those who played \r
+    and are on the scoreboard).\r
+    """\r
+    real_players = 0\r
+\r
+    for events in player_events:\r
+        if is_real_player(events, count_bots):\r
+            real_players += 1\r
+\r
+    return real_players\r
+\r
+\r
+def has_minimum_real_players(settings, player_events):\r
     """\r
     Determines if the collection of player events has enough "real" players\r
     to store in the database. The minimum setting comes from the config file\r
     """\r
     Determines if the collection of player events has enough "real" players\r
     to store in the database. The minimum setting comes from the config file\r
@@ -40,17 +92,13 @@ def has_minimum_real_players(player_events):
     """\r
     flg_has_min_real_players = True\r
 \r
     """\r
     flg_has_min_real_players = True\r
 \r
-    settings = get_current_registry().settings\r
-    try: \r
+    try:\r
         minimum_required_players = int(\r
                 settings['xonstat.minimum_required_players'])\r
     except:\r
         minimum_required_players = 2\r
 \r
         minimum_required_players = int(\r
                 settings['xonstat.minimum_required_players'])\r
     except:\r
         minimum_required_players = 2\r
 \r
-    real_players = 0\r
-    for events in player_events:\r
-        if is_real_player(events):\r
-            real_players += 1\r
+    real_players = num_real_players(player_events)\r
 \r
     #TODO: put this into a config setting in the ini file?\r
     if real_players < minimum_required_players:\r
 \r
     #TODO: put this into a config setting in the ini file?\r
     if real_players < minimum_required_players:\r
@@ -69,16 +117,17 @@ def has_required_metadata(metadata):
     if 'T' not in metadata or\\r
         'G' not in metadata or\\r
         'M' not in metadata or\\r
     if 'T' not in metadata or\\r
         'G' not in metadata or\\r
         'M' not in metadata or\\r
+        'I' not in metadata or\\r
         'S' not in metadata:\r
             flg_has_req_metadata = False\r
 \r
     return flg_has_req_metadata\r
 \r
         'S' not in metadata:\r
             flg_has_req_metadata = False\r
 \r
     return flg_has_req_metadata\r
 \r
-    \r
-def is_real_player(events):\r
+\r
+def is_real_player(events, count_bots=False):\r
     """\r
     Determines if a given set of player events correspond with a player who\r
     """\r
     Determines if a given set of player events correspond with a player who\r
-    \r
+\r
     1) is not a bot (P event does not look like a bot)\r
     2) played in the game (matches 1)\r
     3) was present at the end of the game (scoreboardvalid 1)\r
     1) is not a bot (P event does not look like a bot)\r
     2) played in the game (matches 1)\r
     3) was present at the end of the game (scoreboardvalid 1)\r
@@ -87,9 +136,10 @@ def is_real_player(events):
     """\r
     flg_is_real = False\r
 \r
     """\r
     flg_is_real = False\r
 \r
-    if not events['P'].startswith('bot'):\r
-        # removing 'joins' here due to bug, but it should be here\r
-        if 'matches' in events and 'scoreboardvalid' in events:\r
+    # removing 'joins' here due to bug, but it should be here\r
+    if 'matches' in events and 'scoreboardvalid' in events:\r
+        if (events['P'].startswith('bot') and count_bots) or \\r
+            not events['P'].startswith('bot'):\r
             flg_is_real = True\r
 \r
     return flg_is_real\r
             flg_is_real = True\r
 \r
     return flg_is_real\r
@@ -107,24 +157,26 @@ def register_new_nick(session, player, new_nick):
     # see if that nick already exists\r
     stripped_nick = strip_colors(player.nick)\r
     try:\r
     # see if that nick already exists\r
     stripped_nick = strip_colors(player.nick)\r
     try:\r
-       player_nick = session.query(PlayerNick).filter_by(\r
-               player_id=player.player_id, stripped_nick=stripped_nick).one()\r
+        player_nick = session.query(PlayerNick).filter_by(\r
+            player_id=player.player_id, stripped_nick=stripped_nick).one()\r
     except NoResultFound, e:\r
     except NoResultFound, e:\r
-           # player_id/stripped_nick not found, create one\r
+        # player_id/stripped_nick not found, create one\r
         # but we don't store "Anonymous Player #N"\r
         if not re.search('^Anonymous Player #\d+$', player.nick):\r
         # but we don't store "Anonymous Player #N"\r
         if not re.search('^Anonymous Player #\d+$', player.nick):\r
-           player_nick = PlayerNick()\r
+            player_nick = PlayerNick()\r
             player_nick.player_id = player.player_id\r
             player_nick.player_id = player.player_id\r
-            player_nick.stripped_nick = stripped_nick\r
+            player_nick.stripped_nick = player.stripped_nick\r
             player_nick.nick = player.nick\r
             session.add(player_nick)\r
 \r
     # We change to the new nick regardless\r
     player.nick = new_nick\r
             player_nick.nick = player.nick\r
             session.add(player_nick)\r
 \r
     # We change to the new nick regardless\r
     player.nick = new_nick\r
+    player.stripped_nick = strip_colors(new_nick)\r
     session.add(player)\r
 \r
 \r
     session.add(player)\r
 \r
 \r
-def get_or_create_server(session=None, name=None, hashkey=None):\r
+def get_or_create_server(session=None, name=None, hashkey=None, ip_addr=None,\r
+        revision=None):\r
     """\r
     Find a server by name or create one if not found. Parameters:\r
 \r
     """\r
     Find a server by name or create one if not found. Parameters:\r
 \r
@@ -132,26 +184,40 @@ def get_or_create_server(session=None, name=None, hashkey=None):
     name - server name of the server to be found or created\r
     hashkey - server hashkey\r
     """\r
     name - server name of the server to be found or created\r
     hashkey - server hashkey\r
     """\r
-    # see if the server is already in the database\r
-    # if not, create one and the hashkey along with it\r
     try:\r
     try:\r
-        hashkey = session.query(ServerHashkey).filter_by(\r
-                hashkey=hashkey).one()\r
-        server = session.query(Server).filter_by(\r
-                server_id=hashkey.server_id).one()\r
-        log.debug("Found existing server {0} with hashkey {1}".format(\r
-            server.server_id, hashkey.hashkey))\r
-    except:\r
-        server = Server()\r
-        server.name = name\r
+        # find one by that name, if it exists\r
+        server = session.query(Server).filter_by(name=name).one()\r
+\r
+        # store new hashkey\r
+        if server.hashkey != hashkey:\r
+            server.hashkey = hashkey\r
+            session.add(server)\r
+\r
+        # store new IP address\r
+        if server.ip_addr != ip_addr:\r
+            server.ip_addr = ip_addr\r
+            session.add(server)\r
+\r
+        # store new revision\r
+        if server.revision != revision:\r
+            server.revision = revision\r
+            session.add(server)\r
+\r
+        log.debug("Found existing server {0}".format(server.server_id))\r
+\r
+    except MultipleResultsFound, e:\r
+        # multiple found, so also filter by hashkey\r
+        server = session.query(Server).filter_by(name=name).\\r
+                filter_by(hashkey=hashkey).one()\r
+        log.debug("Found existing server {0}".format(server.server_id))\r
+\r
+    except NoResultFound, e:\r
+        # not found, create one\r
+        server = Server(name=name, hashkey=hashkey)\r
         session.add(server)\r
         session.flush()\r
         session.add(server)\r
         session.flush()\r
-\r
-        hashkey = ServerHashkey(server_id=server.server_id, \r
-                hashkey=hashkey)\r
-        session.add(hashkey)\r
         log.debug("Created server {0} with hashkey {1}".format(\r
         log.debug("Created server {0} with hashkey {1}".format(\r
-            server.server_id, hashkey.hashkey))\r
+            server.server_id, server.hashkey))\r
 \r
     return server\r
 \r
 \r
     return server\r
 \r
@@ -187,7 +253,7 @@ def get_or_create_map(session=None, name=None):
 \r
 \r
 def create_game(session=None, start_dt=None, game_type_cd=None, \r
 \r
 \r
 def create_game(session=None, start_dt=None, game_type_cd=None, \r
-        server_id=None, map_id=None, winner=None):\r
+        server_id=None, map_id=None, winner=None, match_id=None):\r
     """\r
     Creates a game. Parameters:\r
 \r
     """\r
     Creates a game. Parameters:\r
 \r
@@ -198,14 +264,24 @@ def create_game(session=None, start_dt=None, game_type_cd=None,
     map_id - map on which the game was played\r
     winner - the team id of the team that won\r
     """\r
     map_id - map on which the game was played\r
     winner - the team id of the team that won\r
     """\r
-\r
-    game = Game(start_dt=start_dt, game_type_cd=game_type_cd,\r
+    seq = Sequence('games_game_id_seq')\r
+    game_id = session.execute(seq)\r
+    game = Game(game_id=game_id, start_dt=start_dt, game_type_cd=game_type_cd,\r
                 server_id=server_id, map_id=map_id, winner=winner)\r
                 server_id=server_id, map_id=map_id, winner=winner)\r
-    session.add(game)\r
-    session.flush()\r
-    log.debug("Created game id {0} on server {1}, map {2} at \\r
-            {3}".format(game.game_id, \r
-                server_id, map_id, start_dt))\r
+    game.match_id = match_id\r
+\r
+    try:\r
+        session.query(Game).filter(Game.server_id==server_id).\\r
+                filter(Game.match_id==match_id).one()\r
+        # if a game under the same server and match_id found, \r
+        # this is a duplicate game and can be ignored\r
+        raise pyramid.httpexceptions.HTTPOk('OK')\r
+    except NoResultFound, e:\r
+        # server_id/match_id combination not found. game is ok to insert\r
+        session.add(game)\r
+        log.debug("Created game id {0} on server {1}, map {2} at \\r
+                {3}".format(game.game_id, \r
+                    server_id, map_id, start_dt))\r
 \r
     return game\r
 \r
 \r
     return game\r
 \r
@@ -230,28 +306,30 @@ def get_or_create_player(session=None, hashkey=None, nick=None):
         # see if the player is already in the database\r
         # if not, create one and the hashkey along with it\r
         try:\r
         # see if the player is already in the database\r
         # if not, create one and the hashkey along with it\r
         try:\r
-            hashkey = session.query(Hashkey).filter_by(\r
+            hk = session.query(Hashkey).filter_by(\r
                     hashkey=hashkey).one()\r
             player = session.query(Player).filter_by(\r
                     hashkey=hashkey).one()\r
             player = session.query(Player).filter_by(\r
-                    player_id=hashkey.player_id).one()\r
+                    player_id=hk.player_id).one()\r
             log.debug("Found existing player {0} with hashkey {1}".format(\r
             log.debug("Found existing player {0} with hashkey {1}".format(\r
-                player.player_id, hashkey.hashkey))\r
+                player.player_id, hashkey))\r
         except:\r
             player = Player()\r
             session.add(player)\r
             session.flush()\r
 \r
         except:\r
             player = Player()\r
             session.add(player)\r
             session.flush()\r
 \r
-           # if nick is given to us, use it. If not, use "Anonymous Player"\r
+            # if nick is given to us, use it. If not, use "Anonymous Player"\r
             # with a suffix added for uniqueness.\r
             if nick:\r
                 player.nick = nick[:128]\r
             # with a suffix added for uniqueness.\r
             if nick:\r
                 player.nick = nick[:128]\r
-           else:\r
+                player.stripped_nick = strip_colors(nick[:128])\r
+            else:\r
                 player.nick = "Anonymous Player #{0}".format(player.player_id)\r
                 player.nick = "Anonymous Player #{0}".format(player.player_id)\r
+                player.stripped_nick = player.nick\r
 \r
 \r
-            hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)\r
-            session.add(hashkey)\r
+            hk = Hashkey(player_id=player.player_id, hashkey=hashkey)\r
+            session.add(hk)\r
             log.debug("Created player {0} ({2}) with hashkey {1}".format(\r
             log.debug("Created player {0} ({2}) with hashkey {1}".format(\r
-                player.player_id, hashkey.hashkey, player.nick.encode('utf-8')))\r
+                player.player_id, hashkey, player.nick.encode('utf-8')))\r
 \r
     return player\r
 \r
 \r
     return player\r
 \r
@@ -269,7 +347,10 @@ def create_player_game_stat(session=None, player=None,
     # in here setup default values (e.g. if game type is CTF then\r
     # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc\r
     # TODO: use game's create date here instead of now()\r
     # in here setup default values (e.g. if game type is CTF then\r
     # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc\r
     # TODO: use game's create date here instead of now()\r
-    pgstat = PlayerGameStat(create_dt=datetime.datetime.now())\r
+    seq = Sequence('player_game_stats_player_game_stat_id_seq')\r
+    pgstat_id = session.execute(seq)\r
+    pgstat = PlayerGameStat(player_game_stat_id=pgstat_id, \r
+            create_dt=datetime.datetime.utcnow())\r
 \r
     # set player id from player record\r
     pgstat.player_id = player.player_id\r
 \r
     # set player id from player record\r
     pgstat.player_id = player.player_id\r
@@ -280,7 +361,7 @@ def create_player_game_stat(session=None, player=None,
     # all games have a score\r
     pgstat.score = 0\r
 \r
     # all games have a score\r
     pgstat.score = 0\r
 \r
-    if game.game_type_cd == 'dm':\r
+    if game.game_type_cd == 'dm' or game.game_type_cd == 'tdm' or game.game_type_cd == 'duel':\r
         pgstat.kills = 0\r
         pgstat.deaths = 0\r
         pgstat.suicides = 0\r
         pgstat.kills = 0\r
         pgstat.deaths = 0\r
         pgstat.suicides = 0\r
@@ -313,9 +394,12 @@ def create_player_game_stat(session=None, player=None,
     if pgstat.nick == None:\r
         pgstat.nick = player.nick\r
 \r
     if pgstat.nick == None:\r
         pgstat.nick = player.nick\r
 \r
+    # whichever nick we ended up with, strip it and store as the stripped_nick\r
+    pgstat.stripped_nick = qfont_decode(strip_colors(pgstat.nick))\r
+\r
     # if the nick we end up with is different from the one in the\r
     # player record, change the nick to reflect the new value\r
     # if the nick we end up with is different from the one in the\r
     # player record, change the nick to reflect the new value\r
-    if pgstat.nick != player.nick and player.player_id > 1:\r
+    if pgstat.nick != player.nick and player.player_id > 2:\r
         register_new_nick(session, player, pgstat.nick)\r
 \r
     # if the player is ranked #1 and it is a team game, set the game's winner\r
         register_new_nick(session, player, pgstat.nick)\r
 \r
     # if the player is ranked #1 and it is a team game, set the game's winner\r
@@ -326,7 +410,6 @@ def create_player_game_stat(session=None, player=None,
         session.add(game)\r
 \r
     session.add(pgstat)\r
         session.add(game)\r
 \r
     session.add(pgstat)\r
-    session.flush()\r
 \r
     return pgstat\r
 \r
 \r
     return pgstat\r
 \r
@@ -350,7 +433,10 @@ def create_player_weapon_stats(session=None, player=None,
         matched = re.search("acc-(.*?)-cnt-fired", key)\r
         if matched:\r
             weapon_cd = matched.group(1)\r
         matched = re.search("acc-(.*?)-cnt-fired", key)\r
         if matched:\r
             weapon_cd = matched.group(1)\r
+            seq = Sequence('player_weapon_stats_player_weapon_stats_id_seq')\r
+            pwstat_id = session.execute(seq)\r
             pwstat = PlayerWeaponStat()\r
             pwstat = PlayerWeaponStat()\r
+            pwstat.player_weapon_stats_id = pwstat_id\r
             pwstat.player_id = player.player_id\r
             pwstat.game_id = game.game_id\r
             pwstat.player_game_stat_id = pgstat.player_game_stat_id\r
             pwstat.player_id = player.player_id\r
             pwstat.game_id = game.game_id\r
             pwstat.player_game_stat_id = pgstat.player_game_stat_id\r
@@ -392,21 +478,17 @@ def parse_body(request):
     player_events = {}\r
     current_team = None\r
     players = []\r
     player_events = {}\r
     current_team = None\r
     players = []\r
-    \r
-    log.debug("----- BEGIN REQUEST BODY -----")\r
-    log.debug(request.body)\r
-    log.debug("----- END REQUEST BODY -----")\r
 \r
     for line in request.body.split('\n'):\r
         try:\r
             (key, value) = line.strip().split(' ', 1)\r
 \r
             # Server (S) and Nick (n) fields can have international characters.\r
 \r
     for line in request.body.split('\n'):\r
         try:\r
             (key, value) = line.strip().split(' ', 1)\r
 \r
             # Server (S) and Nick (n) fields can have international characters.\r
-            # We encode these as UTF-8.\r
+            # We convert to UTF-8.\r
             if key in 'S' 'n':\r
                 value = unicode(value, 'utf-8')\r
             if key in 'S' 'n':\r
                 value = unicode(value, 'utf-8')\r
-    \r
-            if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':\r
+\r
+            if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W' 'I':\r
                 game_meta[key] = value\r
 \r
             if key == 'P':\r
                 game_meta[key] = value\r
 \r
             if key == 'P':\r
@@ -415,7 +497,7 @@ def parse_body(request):
                 if len(player_events) != 0:\r
                     players.append(player_events)\r
                     player_events = {}\r
                 if len(player_events) != 0:\r
                     players.append(player_events)\r
                     player_events = {}\r
-    \r
+\r
                 player_events[key] = value\r
 \r
             if key == 'e':\r
                 player_events[key] = value\r
 \r
             if key == 'e':\r
@@ -428,7 +510,7 @@ def parse_body(request):
         except:\r
             # no key/value pair - move on to the next line\r
             pass\r
         except:\r
             # no key/value pair - move on to the next line\r
             pass\r
-    \r
+\r
     # add the last player we were working on\r
     if len(player_events) > 0:\r
         players.append(player_events)\r
     # add the last player we were working on\r
     if len(player_events) > 0:\r
         players.append(player_events)\r
@@ -449,44 +531,63 @@ def create_player_stats(session=None, player=None, game=None,
         create_player_weapon_stats(session=session, \r
             player=player, game=game, pgstat=pgstat,\r
             player_events=player_events)\r
         create_player_weapon_stats(session=session, \r
             player=player, game=game, pgstat=pgstat,\r
             player_events=player_events)\r
-    \r
+\r
 \r
 def stats_submit(request):\r
     """\r
     Entry handler for POST stats submissions.\r
     """\r
     try:\r
 \r
 def stats_submit(request):\r
     """\r
     Entry handler for POST stats submissions.\r
     """\r
     try:\r
+        session = DBSession()\r
+\r
+        log.debug("\n----- BEGIN REQUEST BODY -----\n" + request.body +\r
+                "----- END REQUEST BODY -----\n\n")\r
+\r
         (idfp, status) = verify_request(request)\r
         if not idfp:\r
         (idfp, status) = verify_request(request)\r
         if not idfp:\r
-            raise Exception("Request is not verified.")\r
-\r
-        session = DBSession()\r
+            log.debug("ERROR: Unverified request")\r
+            raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")\r
 \r
         (game_meta, players) = parse_body(request)  \r
 \r
         (game_meta, players) = parse_body(request)  \r
-    \r
+\r
         if not has_required_metadata(game_meta):\r
         if not has_required_metadata(game_meta):\r
-            log.debug("Required game meta fields (T, G, M, or S) missing. "\\r
-                    "Can't continue.")\r
-            raise Exception("Required game meta fields (T, G, M, or S) missing.")\r
-   \r
+            log.debug("ERROR: Required game meta missing")\r
+            raise pyramid.exceptions.HTTPUnprocessableEntity("Missing game meta")\r
+\r
         if not is_supported_gametype(game_meta['G']):\r
         if not is_supported_gametype(game_meta['G']):\r
-            raise Exception("Gametype not supported.")\r
-     \r
-        if not has_minimum_real_players(players):\r
-            raise Exception("The number of real players is below the minimum. "\\r
-                    "Stats will be ignored.")\r
+            log.debug("ERROR: Unsupported gametype")\r
+            raise pyramid.httpexceptions.HTTPOk("OK")\r
+\r
+        if not has_minimum_real_players(request.registry.settings, players):\r
+            log.debug("ERROR: Not enough real players")\r
+            raise pyramid.httpexceptions.HTTPOk("OK")\r
+\r
+        if is_blank_game(players):\r
+            log.debug("ERROR: Blank game")\r
+            raise pyramid.httpexceptions.HTTPOk("OK")\r
+\r
+        # FIXME: if we have two players and game type is 'dm',\r
+        # change this into a 'duel' gametype. This should be\r
+        # removed when the stats actually send 'duel' instead of 'dm'\r
+        if num_real_players(players, count_bots=True) == 2 and \\r
+                game_meta['G'] == 'dm':\r
+            game_meta['G'] = 'duel'\r
 \r
         server = get_or_create_server(session=session, hashkey=idfp, \r
 \r
         server = get_or_create_server(session=session, hashkey=idfp, \r
-                name=game_meta['S'])\r
+                name=game_meta['S'], revision=game_meta['R'],\r
+                ip_addr=get_remote_addr(request))\r
 \r
         gmap = get_or_create_map(session=session, name=game_meta['M'])\r
 \r
 \r
         gmap = get_or_create_map(session=session, name=game_meta['M'])\r
 \r
+        # FIXME: use the gmtime instead of utcnow() when the timezone bug is\r
+        # fixed\r
         game = create_game(session=session, \r
         game = create_game(session=session, \r
-                start_dt=datetime.datetime(\r
-                    *time.gmtime(float(game_meta['T']))[:6]), \r
+                start_dt=datetime.datetime.utcnow(),\r
+                #start_dt=datetime.datetime(\r
+                    #*time.gmtime(float(game_meta['T']))[:6]), \r
                 server_id=server.server_id, game_type_cd=game_meta['G'], \r
                 server_id=server.server_id, game_type_cd=game_meta['G'], \r
-                map_id=gmap.map_id)\r
-    \r
+                   map_id=gmap.map_id, match_id=game_meta['I'])\r
+\r
         # find or create a record for each player\r
         # and add stats for each if they were present at the end\r
         # of the game\r
         # find or create a record for each player\r
         # and add stats for each if they were present at the end\r
         # of the game\r
@@ -497,16 +598,22 @@ def stats_submit(request):
                 nick = None\r
 \r
             if 'matches' in player_events and 'scoreboardvalid' \\r
                 nick = None\r
 \r
             if 'matches' in player_events and 'scoreboardvalid' \\r
-                    in player_events:\r
+                in player_events:\r
                 player = get_or_create_player(session=session, \r
                     hashkey=player_events['P'], nick=nick)\r
                 log.debug('Creating stats for %s' % player_events['P'])\r
                 create_player_stats(session=session, player=player, game=game, \r
                         player_events=player_events)\r
                 player = get_or_create_player(session=session, \r
                     hashkey=player_events['P'], nick=nick)\r
                 log.debug('Creating stats for %s' % player_events['P'])\r
                 create_player_stats(session=session, player=player, game=game, \r
                         player_events=player_events)\r
-    \r
+\r
+        # update elos\r
+        try:\r
+            game.process_elos(session)\r
+        except Exception as e:\r
+            log.debug('Error (non-fatal): elo processing failed.')\r
+\r
         session.commit()\r
         log.debug('Success! Stats recorded.')\r
         return Response('200 OK')\r
     except Exception as e:\r
         session.rollback()\r
         session.commit()\r
         log.debug('Success! Stats recorded.')\r
         return Response('200 OK')\r
     except Exception as e:\r
         session.rollback()\r
-        raise e\r
+        return e\r