]> de.git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/views/submission.py
clean dangling commit on actually merged branch
[xonotic/xonstat.git] / xonstat / views / submission.py
index 88c4bb2839de9f72cb2d24de2668de7f23101e9b..631d93b0f3bb3207c46599ea56af761ff5e86348 100755 (executable)
@@ -1,16 +1,90 @@
 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
 from pyramid.response import Response\r
 import re\r
 import time\r
 from pyramid.response import Response\r
+from sqlalchemy import Sequence\r
 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound\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.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
 \r
 log = logging.getLogger(__name__)\r
 \r
 \r
-def has_minimum_real_players(player_events):\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
+\r
+    if gametype == 'cts' or gametype == 'lms':\r
+        flg_supported = False\r
+\r
+    return flg_supported\r
+\r
+\r
+def verify_request(request):\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
+        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
+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
@@ -18,13 +92,16 @@ 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
-    real_players = 0\r
-    for events in player_events:\r
-        if is_real_player(events):\r
-            real_players += 1\r
+    try:\r
+        minimum_required_players = int(\r
+                settings['xonstat.minimum_required_players'])\r
+    except:\r
+        minimum_required_players = 2\r
+\r
+    real_players = num_real_players(player_events)\r
 \r
     #TODO: put this into a config setting in the ini file?\r
 \r
     #TODO: put this into a config setting in the ini file?\r
-    if real_players < 1:\r
+    if real_players < minimum_required_players:\r
         flg_has_min_real_players = False\r
 \r
     return flg_has_min_real_players\r
         flg_has_min_real_players = False\r
 \r
     return flg_has_min_real_players\r
@@ -37,19 +114,20 @@ def has_required_metadata(metadata):
     """\r
     flg_has_req_metadata = True\r
 \r
     """\r
     flg_has_req_metadata = True\r
 \r
-    if 'T' not in game_meta or\\r
-        'G' not in game_meta or\\r
-        'M' not in game_meta or\\r
-        'S' not in game_meta:\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
             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
@@ -58,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
@@ -78,53 +157,71 @@ 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):\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
     session - SQLAlchemy database session factory\r
     name - server name of the server to be found or created\r
     """\r
     Find a server by name or create one if not found. Parameters:\r
 \r
     session - SQLAlchemy database session factory\r
     name - server name of the server to be found or created\r
+    hashkey - server hashkey\r
     """\r
     try:\r
         # find one by that name, if it exists\r
         server = session.query(Server).filter_by(name=name).one()\r
     """\r
     try:\r
         # find one by that name, if it exists\r
         server = session.query(Server).filter_by(name=name).one()\r
-        log.debug("Found server id {0}: {1}".format(\r
-            server.server_id, server.name.encode('utf-8')))\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
     except NoResultFound, e:\r
-        server = Server(name=name)\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
-        log.debug("Created server id {0}: {1}".format(\r
-            server.server_id, server.name.encode('utf-8')))\r
-    except MultipleResultsFound, e:\r
-        # multiple found, so use the first one but warn\r
-        log.debug(e)\r
-        servers = session.query(Server).filter_by(name=name).order_by(\r
-                Server.server_id).all()\r
-        server = servers[0]\r
-        log.debug("Created server id {0}: {1} but found \\r
-                multiple".format(\r
-            server.server_id, server.name.encode('utf-8')))\r
+        log.debug("Created server {0} with hashkey {1}".format(\r
+            server.server_id, server.hashkey))\r
 \r
     return server\r
 \r
 \r
     return server\r
 \r
+\r
 def get_or_create_map(session=None, name=None):\r
     """\r
     Find a map by name or create one if not found. Parameters:\r
 def get_or_create_map(session=None, name=None):\r
     """\r
     Find a map by name or create one if not found. Parameters:\r
@@ -156,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
@@ -167,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
@@ -189,7 +296,7 @@ def get_or_create_player(session=None, hashkey=None, nick=None):
     nick - nick of the player (in case of a first time create)\r
     """\r
     # if we have a bot\r
     nick - nick of the player (in case of a first time create)\r
     """\r
     # if we have a bot\r
-    if re.search('^bot#\d+$', hashkey):\r
+    if re.search('^bot#\d+$', hashkey) or re.search('^bot#\d+#', hashkey):\r
         player = session.query(Player).filter_by(player_id=1).one()\r
     # if we have an untracked player\r
     elif re.search('^player#\d+$', hashkey):\r
         player = session.query(Player).filter_by(player_id=1).one()\r
     # if we have an untracked player\r
     elif re.search('^player#\d+$', hashkey):\r
@@ -199,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
@@ -238,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
@@ -249,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
@@ -277,14 +389,14 @@ def create_player_game_stat(session=None, player=None,
         if key == 'scoreboard-kills': pgstat.kills = value\r
         if key == 'scoreboard-suicides': pgstat.suicides = value\r
 \r
         if key == 'scoreboard-kills': pgstat.kills = value\r
         if key == 'scoreboard-suicides': pgstat.suicides = value\r
 \r
-    # check to see if we had a name, and if \r
-    # not use the name from the player id\r
+    # check to see if we had a name, and if\r
+    # not use an anonymous handle\r
     if pgstat.nick == None:\r
     if pgstat.nick == None:\r
-        pgstat.nick = player.nick\r
+        pgstat.nick = "Anonymous Player"\r
+        pgstat.stripped_nick = "Anonymous Player"\r
 \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 pgstat.nick != player.nick and player.player_id > 1:\r
+    # otherwise process a nick change\r
+    elif 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
@@ -295,7 +407,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
@@ -319,7 +430,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
@@ -361,19 +475,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(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
@@ -382,7 +494,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
@@ -395,7 +507,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
@@ -416,7 +528,7 @@ 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
 \r
 def stats_submit(request):\r
     """\r
@@ -425,26 +537,54 @@ def stats_submit(request):
     try:\r
         session = DBSession()\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
+            log.debug("ERROR: Unverified request")\r
+            raise pyramid.httpexceptions.HTTPUnauthorized("Unverified request")\r
+\r
         (game_meta, players) = parse_body(request)  \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
-        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
-\r
-        server = get_or_create_server(session=session, name=game_meta['S'])\r
+            log.debug("ERROR: Required game meta missing")\r
+            raise pyramid.httpexceptions.HTTPUnprocessableEntity("Missing game meta")\r
+\r
+        if not is_supported_gametype(game_meta['G']):\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
+                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
         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, winner=winner)\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
@@ -455,16 +595,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