]> de.git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/views/submission.py
Check for distinct weapons fired during the match.
[xonotic/xonstat.git] / xonstat / views / submission.py
index 2be4a27c8a7bd7a4e233b17386ffe1a8ff204509..81e639e531fcf0f0ba0894ad79fce080614a7100 100644 (file)
@@ -1,4 +1,5 @@
 import calendar
+import collections
 import datetime
 import logging
 import re
@@ -15,6 +16,109 @@ from xonstat.util import strip_colors, qfont_decode, verify_request, weapon_map
 log = logging.getLogger(__name__)
 
 
+class Submission(object):
+    """Parses an incoming POST request for stats submissions."""
+
+    def __init__(self, body, headers):
+        # a copy of the HTTP headers
+        self.headers = headers
+
+        # a copy of the HTTP POST body
+        self.body = body
+
+        # game metadata
+        self.meta = {}
+
+        # raw player events
+        self.players = []
+
+        # raw team events
+        self.teams = []
+
+        # distinct weapons that we have seen fired
+        self.weapons = set()
+
+        # the parsing deque (we use this to allow peeking)
+        self.q = collections.deque(self.body.split("\n"))
+
+    def next_item(self):
+        """Returns the next key:value pair off the queue."""
+        try:
+            items = self.q.popleft().strip().split(' ', 1)
+            if len(items) == 1:
+                return None, None
+            else:
+                return items
+        except:
+            return None, None
+
+    def check_for_new_weapon_fired(self, sub_key):
+        """Checks if a given player key (subkey, actually) is a new weapon fired in the match."""
+        if sub_key.endswith("cnt-fired"):
+            weapon = sub_key.split("-")[1]
+            if weapon not in self.weapons:
+                self.weapons.add(weapon)
+
+    def parse_player(self, key, pid):
+        """Construct a player events listing from the submission."""
+
+        # all of the keys related to player records
+        player_keys = ['i', 'n', 't', 'e']
+
+        player = {key: pid}
+
+        # Consume all following 'i' 'n' 't'  'e' records
+        while len(self.q) > 0:
+            (key, value) = self.next_item()
+            if key is None and value is None:
+                continue
+            elif key == 'e':
+                (sub_key, sub_value) = value.split(' ', 1)
+                player[sub_key] = sub_value
+
+                # keep track of the distinct weapons fired during the match
+                self.check_for_new_weapon_fired(sub_key)
+            elif key == 'n':
+                player[key] = unicode(value, 'utf-8')
+            elif key in player_keys:
+                player[key] = value
+            else:
+                # something we didn't expect - put it back on the deque
+                self.q.appendleft("{} {}".format(key, value))
+                break
+
+        self.players.append(player)
+
+    def parse_team(self, key, tid):
+        """Construct a team events listing from the submission."""
+        team = {key: tid}
+
+        # Consume all following 'e' records
+        while len(self.q) > 0 and self.q[0].startswith('e'):
+            (_, value) = self.next_item()
+            (sub_key, sub_value) = value.split(' ', 1)
+            team[sub_key] = sub_value
+
+        self.teams.append(team)
+
+    def parse(self):
+        """Parses the request body into instance variables."""
+        while len(self.q) > 0:
+            (key, value) = self.next_item()
+            if key is None and value is None:
+                continue
+            elif key == 'S':
+                self.meta[key] = unicode(value, 'utf-8')
+            elif key == 'P':
+                self.parse_player(key, value)
+            elif key == 'Q':
+                self.parse_team(key, value)
+            else:
+                self.meta[key] = value
+
+        return self
+
+
 def parse_stats_submission(body):
     """
     Parses the POST request body for a stats submission
@@ -149,6 +253,7 @@ def is_supported_gametype(gametype, version):
             'cts',
             'dm',
             'dom',
+            'duel',
             'ft', 'freezetag',
             'ka', 'keepaway',
             'kh',