]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views.py
Add server info view. Do a try/except block around the whole stats submit to allow...
[xonotic/xonstat.git] / xonstat / views.py
1 import datetime
2 import re
3 from pyramid.response import Response
4 from pyramid.view import view_config
5
6 from xonstat.models import *
7 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
8
9
10 import logging
11 log = logging.getLogger(__name__)
12
13 ##########################################################################
14 # This is the main index - the entry point to the entire site
15 ##########################################################################
16 @view_config(renderer='index.jinja2')
17 def main_index(request):
18     log.debug("testing logging; entered MainHandler.index()")
19     return {'project':'xonstat'}
20
21 ##########################################################################
22 # This is the player views area - only views pertaining to Xonotic players
23 # and their related information goes here
24 ##########################################################################
25 @view_config(renderer='player_index.mako')
26 def player_index(request):
27     players = DBSession.query(Player)
28     log.debug("testing logging; entered PlayerHandler.index()")
29     return {'players':players}
30
31 @view_config(renderer='player_info.mako')
32 def player_info(request):
33     player_id = request.matchdict['id']
34     try:
35         player = DBSession.query(Player).filter_by(player_id=player_id).one()
36     except:
37         player = None
38     return {'player':player}
39
40
41 ##########################################################################
42 # This is the game views area - only views pertaining to Xonotic
43 # games and their related information goes here
44 ##########################################################################
45 def game_info(request):
46     game_id = request.matchdict['id']
47     try:
48         game = DBSession.query(Game).filter_by(game_id=game_id).one()
49     except:
50         game = None
51     return {'game':game}
52
53
54 ##########################################################################
55 # This is the server views area - only views pertaining to Xonotic
56 # servers and their related information goes here
57 ##########################################################################
58 def server_info(request):
59     server_id = request.matchdict['id']
60     try:
61         server = DBSession.query(Server).filter_by(server_id=server_id).one()
62     except:
63         server = None
64     return {'server':server}
65
66
67 ##########################################################################
68 # This is the stats views area - only views pertaining to Xonotic
69 # statistics and its related information goes here
70 ##########################################################################
71 def get_or_create_server(session=None, name=None):
72     try:
73         # find one by that name, if it exists
74         server = session.query(Server).filter_by(name=name).one()
75         log.debug("Found server id {0} with name {1}.".format(
76             server.server_id, server.name))
77     except NoResultFound, e:
78         server = Server(name=name)
79         session.add(server)
80         session.flush()
81         log.debug("Created server id {0} with name {1}".format(
82             server.server_id, server.name))
83     except MultipleResultsFound, e:
84         # multiple found, so use the first one but warn
85         log.debug(e)
86         servers = session.query(Server).filter_by(name=name).order_by(
87                 Server.server_id).all()
88         server = servers[0]
89         log.debug("Created server id {0} with name {1} but found \
90                 multiple".format(
91             server.server_id, server.name))
92
93     return server
94
95 def get_or_create_map(session=None, name=None):
96     try:
97         # find one by the name, if it exists
98         gmap = session.query(Map).filter_by(name=name).one()
99         log.debug("Found map id {0} with name {1}.".format(gmap.map_id, 
100             gmap.name))
101     except NoResultFound, e:
102         gmap = Map(name=name)
103         session.add(gmap)
104         session.flush()
105         log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
106             gmap.name))
107     except MultipleResultsFound, e:
108         # multiple found, so use the first one but warn
109         log.debug(e)
110         gmaps = session.query(Map).filter_by(name=name).order_by(
111                 Map.map_id).all()
112         gmap = gmaps[0]
113         log.debug("Found map id {0} with name {1} but found \
114                 multiple.".format(gmap.map_id, gmap.name))
115
116     return gmap
117
118 def create_game(session=None, start_dt=None, game_type_cd=None, 
119         server_id=None, map_id=None, winner=None):
120     game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
121                 server_id=server_id, map_id=map_id, winner=winner)
122     session.add(game)
123     session.flush()
124     log.debug("Created game id {0} on server {1}, map {2} at time \
125             {3} and on map {4}".format(game.game_id, 
126                 server_id, map_id, start_dt, map_id))
127
128     return game
129
130 # search for a player and if found, create a new one (w/ hashkey)
131 def get_or_create_player(session=None, hashkey=None):
132     # if we have a bot
133     if re.search('^bot#\d+$', hashkey):
134         player = session.query(Player).filter_by(player_id=1).one()
135     # if we have an untracked player
136     elif re.search('^player#\d+$', hashkey):
137         player = session.query(Player).filter_by(player_id=2).one()
138     # else it is a tracked player
139     else:
140         # see if the player is already in the database
141         # if not, create one and the hashkey along with it
142         try:
143             hashkey = session.query(Hashkey).filter_by(
144                     hashkey=hashkey).one()
145             player = session.query(Player).filter_by(
146                     player_id=hashkey.player_id).one()
147             log.debug("Found existing player {0} with hashkey {1}.".format(
148                 player.player_id, hashkey.hashkey))
149         except:
150             player = Player()
151             session.add(player)
152             session.flush()
153             hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
154             session.add(hashkey)
155             log.debug("Created player {0} with hashkey {1}.".format(
156                 player.player_id, hashkey.hashkey))
157
158     return player
159
160 def create_player_game_stat(session=None, player=None, 
161         game=None, player_events=None):
162
163     # in here setup default values (e.g. if game type is CTF then
164     # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
165     # TODO: use game's create date here instead of now()
166     pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
167
168     # set player id from player record
169     pgstat.player_id = player.player_id
170
171     #set game id from game record
172     pgstat.game_id = game.game_id
173
174     # all games have a score
175     pgstat.score = 0
176
177     if game.game_type_cd == 'dm':
178         pgstat.kills = 0
179         pgstat.deaths = 0
180         pgstat.suicides = 0
181     elif game.game_type_cd == 'ctf':
182         pgstat.kills = 0
183         pgstat.captures = 0
184         pgstat.pickups = 0
185         pgstat.drops = 0
186         pgstat.returns = 0
187         pgstat.carrier_frags = 0
188
189     for (key,value) in player_events.items():
190         if key == 'n': pgstat.nick = value
191         if key == 't': pgstat.team = value
192         if key == 'rank': pgstat.rank = value
193         if key == 'alivetime': 
194             pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
195         if key == 'scoreboard-drops': pgstat.drops = value
196         if key == 'scoreboard-returns': pgstat.returns = value
197         if key == 'scoreboard-fckills': pgstat.carrier_frags = value
198         if key == 'scoreboard-pickups': pgstat.pickups = value
199         if key == 'scoreboard-caps': pgstat.captures = value
200         if key == 'scoreboard-score': pgstat.score = value
201         if key == 'scoreboard-deaths': pgstat.deaths = value
202         if key == 'scoreboard-kills': pgstat.kills = value
203         if key == 'scoreboard-suicides': pgstat.suicides = value
204
205     # check to see if we had a name, and if 
206     # not use the name from the player id
207     if pgstat.nick == None:
208         pgstat.nick = player.nick
209
210     session.add(pgstat)
211     session.flush()
212
213     return pgstat
214
215
216 def create_player_weapon_stats(session=None, player=None, 
217         game=None, player_events=None):
218     pwstats = []
219
220     for (key,value) in player_events.items():
221         matched = re.search("acc-(.*?)-cnt-fired", key)
222         if matched:
223             log.debug("Matched key: {0}".format(key))
224             weapon_cd = matched.group(1)
225             pwstat = PlayerWeaponStat()
226             pwstat.player_id = player.player_id
227             pwstat.game_id = game.game_id
228             pwstat.weapon_cd = weapon_cd
229             try:
230                 pwstat.max = int(player_events['acc-' + weapon_cd + '-fired'])
231             except:
232                 pwstat.max = 0
233             try:
234                 pwstat.actual = int(player_events['acc-' + weapon_cd + '-hit'])
235             except:
236                 pwstat.actual = 0
237             try:
238                 pwstat.fired = int(player_events['acc-' + weapon_cd + '-cnt-fired'])
239             except:
240                 pwstat.fired = 0
241             try:
242                 pwstat.hit = int(player_events['acc-' + weapon_cd + '-cnt-hit'])
243             except:
244                 pwstat.hit = 0
245             try:
246                 pwstat.frags = int(player_events['acc-' + weapon_cd + '-frags'])
247             except:
248                 pwstat.frags = 0
249
250             session.add(pwstat)
251             pwstats.append(pwstat)
252
253     return pwstats
254
255
256 def parse_body(request):
257     # storage vars for the request body
258     game_meta = {}
259     player_events = {}
260     current_team = None
261     players = []
262     
263     log.debug(request.body)
264
265     for line in request.body.split('\n'):
266         try:
267             (key, value) = line.strip().split(' ', 1)
268     
269             if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
270                 game_meta[key] = value
271
272             if key == 't':
273                 current_team = value
274     
275             if key == 'P':
276                 # if we were working on a player record already, append
277                 # it and work on a new one (only set team info)
278                 if len(player_events) != 0:
279                     players.append(player_events)
280                     player_events = {'t':current_team}
281     
282                 player_events[key] = value
283     
284             if key == 'e':
285                 (subkey, subvalue) = value.split(' ', 1)
286                 player_events[subkey] = subvalue
287
288             if key == 'n':
289                 player_events[key] = value
290         except:
291             # no key/value pair - move on to the next line
292             pass
293     
294     # add the last player we were working on
295     if len(player_events) > 0:
296         players.append(player_events)
297
298     return (game_meta, players)
299
300
301 @view_config(renderer='stats_submit.mako')
302 def stats_submit(request):
303     try:
304         session = DBSession()
305
306         (game_meta, players) = parse_body(request)  
307     
308         # verify required metadata is present
309         if 'T' not in game_meta or\
310             'G' not in game_meta or\
311             'M' not in game_meta or\
312             'S' not in game_meta:
313             log.debug("Required game meta fields (T, G, M, or S) missing. "\
314                     "Can't continue.")
315             return {'msg':'Error processing the request.'}
316     
317         server = get_or_create_server(session=session, name=game_meta['S'])
318         gmap = get_or_create_map(session=session, name=game_meta['M'])
319
320         if 'W' in game_meta:
321             winner = game_meta['W']
322         else:
323             winner = None
324
325         # FIXME: don't use python now() here, convert from epoch T value
326         game = create_game(session=session, start_dt=datetime.datetime.now(), 
327                 server_id=server.server_id, game_type_cd=game_meta['G'], 
328                 map_id=gmap.map_id, winner=winner)
329     
330         # find or create a record for each player
331         # and add stats for each if they were present at the end
332         # of the game
333         has_real_players = False
334         for player_events in players:
335             if not player_events['P'].startswith('bot'):
336                 has_real_players = True
337             player = get_or_create_player(session=session, 
338                     hashkey=player_events['P'])
339             if 'joins' in player_events and 'matches' in player_events\
340                     and 'scoreboardvalid' in player_events:
341                 pgstat = create_player_game_stat(session=session, 
342                         player=player, game=game, player_events=player_events)
343                 #pwstats = create_player_weapon_stats(session=session, 
344                         #player=player, game=game, player_events=player_events)
345     
346         if has_real_players:
347             session.commit()
348             log.debug('Success! Stats recorded.')
349             return Response('200 OK')
350         else:
351             session.rollback()
352             log.debug('No real players found. Stats ignored.')
353             return {'msg':'No real players found. Stats ignored.'}
354     except Exception as e:
355         session.rollback()
356         raise e