]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views.py
Git rid of unnecessary debug lines that I forgot to remove before.
[xonotic/xonstat.git] / xonstat / views.py
1 import datetime
2 import time
3 import re
4 from pyramid.response import Response
5 from pyramid.view import view_config
6 from webhelpers.paginate import Page, PageURL
7
8 from xonstat.models import *
9 from xonstat.util import page_url
10 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
11 from sqlalchemy import desc
12
13
14 import logging
15 log = logging.getLogger(__name__)
16
17 ##########################################################################
18 # This is the main index - the entry point to the entire site
19 ##########################################################################
20 @view_config(renderer='index.jinja2')
21 def main_index(request):
22     log.debug("testing logging; entered MainHandler.index()")
23     return {'project':'xonstat'}
24
25 ##########################################################################
26 # This is the player views area - only views pertaining to Xonotic players
27 # and their related information goes here
28 ##########################################################################
29 @view_config(renderer='player_index.mako')
30 def player_index(request):
31     players = DBSession.query(Player)
32
33     log.debug("testing logging; entered PlayerHandler.index()")
34     return {'players':players}
35
36 @view_config(renderer='player_info.mako')
37 def player_info(request):
38     player_id = request.matchdict['id']
39     try:
40         player = DBSession.query(Player).filter_by(player_id=player_id).one()
41         recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\
42                 filter(PlayerGameStat.player_id == player_id).\
43                 filter(PlayerGameStat.game_id == Game.game_id).\
44                 filter(Game.server_id == Server.server_id).\
45                 filter(Game.map_id == Map.map_id).\
46                 order_by(Game.game_id.desc())[0:10]
47
48     except Exception as e:
49         player = None
50         recent_games = None
51     return {'player':player, 
52             'recent_games':recent_games}
53
54
55 def player_game_index(request):
56     player_id = request.matchdict['player_id']
57     current_page = request.matchdict['page']
58
59     try:
60         player = DBSession.query(Player).filter_by(player_id=player_id).one()
61
62         games_q = DBSession.query(PlayerGameStat, Game, Server, Map).\
63                 filter(PlayerGameStat.player_id == player_id).\
64                 filter(PlayerGameStat.game_id == Game.game_id).\
65                 filter(Game.server_id == Server.server_id).\
66                 filter(Game.map_id == Map.map_id).\
67                 order_by(Game.game_id.desc())
68
69         games = Page(games_q, current_page, url=page_url)
70
71         
72     except Exception as e:
73         player = None
74         games = None
75         raise e
76
77     return {'player':player,
78             'games':games}
79
80
81 ##########################################################################
82 # This is the game views area - only views pertaining to Xonotic
83 # games and their related information goes here
84 ##########################################################################
85 def game_index(request):
86     if 'page' in request.matchdict:
87         current_page = request.matchdict['page']
88     else:
89         current_page = 1
90
91     games_q = DBSession.query(Game, Server, Map).\
92             filter(Game.server_id == Server.server_id).\
93             filter(Game.map_id == Map.map_id).\
94             order_by(Game.game_id.desc())
95
96     games = Page(games_q, current_page, url=page_url)
97
98     return {'games':games}
99
100
101 def game_info(request):
102     game_id = request.matchdict['id']
103     try:
104         notfound = False
105
106         (start_dt, game_type_cd, server_id, server_name, map_id, map_name) = \
107         DBSession.query("start_dt", "game_type_cd", "server_id", 
108                 "server_name", "map_id", "map_name").\
109                 from_statement("select g.start_dt, g.game_type_cd, "
110                         "g.server_id, s.name as server_name, g.map_id, "
111                         "m.name as map_name "
112                         "from games g, servers s, maps m "
113                         "where g.game_id = :game_id "
114                         "and g.server_id = s.server_id "
115                         "and g.map_id = m.map_id").\
116                         params(game_id=game_id).one()
117
118         player_game_stats = DBSession.query(PlayerGameStat).\
119                 from_statement("select * from player_game_stats "
120                         "where game_id = :game_id "
121                         "order by score desc").\
122                             params(game_id=game_id).all()
123     except Exception as inst:
124         notfound = True
125         start_dt = None
126         game_type_cd = None
127         server_id = None
128         server_name = None
129         map_id = None
130         map_name = None
131         player_game_stats = None
132
133     return {'notfound':notfound,
134             'start_dt':start_dt,
135             'game_type_cd':game_type_cd,
136             'server_id':server_id,
137             'server_name':server_name,
138             'map_id':map_id,
139             'map_name':map_name,
140             'player_game_stats':player_game_stats}
141
142
143 ##########################################################################
144 # This is the server views area - only views pertaining to Xonotic
145 # servers and their related information goes here
146 ##########################################################################
147 def server_info(request):
148     server_id = request.matchdict['id']
149     try:
150         server = DBSession.query(Server).filter_by(server_id=server_id).one()
151         recent_games = DBSession.query(Game, Server, Map).\
152                 filter(Game.server_id == server_id).\
153                 filter(Game.server_id == Server.server_id).\
154                 filter(Game.map_id == Map.map_id).\
155                 order_by(Game.game_id.desc())[0:10]
156
157     except Exception as e:
158         server = None
159         recent_games = None
160     return {'server':server,
161             'recent_games':recent_games}
162
163
164 def server_game_index(request):
165     server_id = request.matchdict['server_id']
166     current_page = request.matchdict['page']
167
168     try:
169         server = DBSession.query(Server).filter_by(server_id=server_id).one()
170
171         games_q = DBSession.query(Game, Server, Map).\
172                 filter(Game.server_id == server_id).\
173                 filter(Game.server_id == Server.server_id).\
174                 filter(Game.map_id == Map.map_id).\
175                 order_by(Game.game_id.desc())
176
177         games = Page(games_q, current_page, url=page_url)
178     except Exception as e:
179         server = None
180         games = None
181         raise e
182
183     return {'games':games,
184             'server':server}
185
186
187 ##########################################################################
188 # This is the map views area - only views pertaining to Xonotic
189 # maps and their related information goes here
190 ##########################################################################
191 def map_info(request):
192     map_id = request.matchdict['id']
193     try:
194         gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
195     except:
196         gmap = None
197     return {'gmap':gmap}
198
199
200 ##########################################################################
201 # This is the stats views area - only views pertaining to Xonotic
202 # statistics and its related information goes here
203 ##########################################################################
204 def get_or_create_server(session=None, name=None):
205     try:
206         # find one by that name, if it exists
207         server = session.query(Server).filter_by(name=name).one()
208         log.debug("Found server id {0} with name {1}.".format(
209             server.server_id, server.name))
210     except NoResultFound, e:
211         server = Server(name=name)
212         session.add(server)
213         session.flush()
214         log.debug("Created server id {0} with name {1}".format(
215             server.server_id, server.name))
216     except MultipleResultsFound, e:
217         # multiple found, so use the first one but warn
218         log.debug(e)
219         servers = session.query(Server).filter_by(name=name).order_by(
220                 Server.server_id).all()
221         server = servers[0]
222         log.debug("Created server id {0} with name {1} but found \
223                 multiple".format(
224             server.server_id, server.name))
225
226     return server
227
228 def get_or_create_map(session=None, name=None):
229     try:
230         # find one by the name, if it exists
231         gmap = session.query(Map).filter_by(name=name).one()
232         log.debug("Found map id {0} with name {1}.".format(gmap.map_id, 
233             gmap.name))
234     except NoResultFound, e:
235         gmap = Map(name=name)
236         session.add(gmap)
237         session.flush()
238         log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
239             gmap.name))
240     except MultipleResultsFound, e:
241         # multiple found, so use the first one but warn
242         log.debug(e)
243         gmaps = session.query(Map).filter_by(name=name).order_by(
244                 Map.map_id).all()
245         gmap = gmaps[0]
246         log.debug("Found map id {0} with name {1} but found \
247                 multiple.".format(gmap.map_id, gmap.name))
248
249     return gmap
250
251 def create_game(session=None, start_dt=None, game_type_cd=None, 
252         server_id=None, map_id=None, winner=None):
253     game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
254                 server_id=server_id, map_id=map_id, winner=winner)
255     session.add(game)
256     session.flush()
257     log.debug("Created game id {0} on server {1}, map {2} at time \
258             {3} and on map {4}".format(game.game_id, 
259                 server_id, map_id, start_dt, map_id))
260
261     return game
262
263 # search for a player and if found, create a new one (w/ hashkey)
264 def get_or_create_player(session=None, hashkey=None):
265     # if we have a bot
266     if re.search('^bot#\d+$', hashkey):
267         player = session.query(Player).filter_by(player_id=1).one()
268     # if we have an untracked player
269     elif re.search('^player#\d+$', hashkey):
270         player = session.query(Player).filter_by(player_id=2).one()
271     # else it is a tracked player
272     else:
273         # see if the player is already in the database
274         # if not, create one and the hashkey along with it
275         try:
276             hashkey = session.query(Hashkey).filter_by(
277                     hashkey=hashkey).one()
278             player = session.query(Player).filter_by(
279                     player_id=hashkey.player_id).one()
280             log.debug("Found existing player {0} with hashkey {1}.".format(
281                 player.player_id, hashkey.hashkey))
282         except:
283             player = Player()
284             session.add(player)
285             session.flush()
286             hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
287             session.add(hashkey)
288             log.debug("Created player {0} with hashkey {1}.".format(
289                 player.player_id, hashkey.hashkey))
290
291     return player
292
293 def create_player_game_stat(session=None, player=None, 
294         game=None, player_events=None):
295
296     # in here setup default values (e.g. if game type is CTF then
297     # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
298     # TODO: use game's create date here instead of now()
299     pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
300
301     # set player id from player record
302     pgstat.player_id = player.player_id
303
304     #set game id from game record
305     pgstat.game_id = game.game_id
306
307     # all games have a score
308     pgstat.score = 0
309
310     if game.game_type_cd == 'dm':
311         pgstat.kills = 0
312         pgstat.deaths = 0
313         pgstat.suicides = 0
314     elif game.game_type_cd == 'ctf':
315         pgstat.kills = 0
316         pgstat.captures = 0
317         pgstat.pickups = 0
318         pgstat.drops = 0
319         pgstat.returns = 0
320         pgstat.carrier_frags = 0
321
322     for (key,value) in player_events.items():
323         if key == 'n': pgstat.nick = value
324         if key == 't': pgstat.team = value
325         if key == 'rank': pgstat.rank = value
326         if key == 'alivetime': 
327             pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
328         if key == 'scoreboard-drops': pgstat.drops = value
329         if key == 'scoreboard-returns': pgstat.returns = value
330         if key == 'scoreboard-fckills': pgstat.carrier_frags = value
331         if key == 'scoreboard-pickups': pgstat.pickups = value
332         if key == 'scoreboard-caps': pgstat.captures = value
333         if key == 'scoreboard-score': pgstat.score = value
334         if key == 'scoreboard-deaths': pgstat.deaths = value
335         if key == 'scoreboard-kills': pgstat.kills = value
336         if key == 'scoreboard-suicides': pgstat.suicides = value
337
338     # check to see if we had a name, and if 
339     # not use the name from the player id
340     if pgstat.nick == None:
341         pgstat.nick = player.nick
342
343     session.add(pgstat)
344     session.flush()
345
346     return pgstat
347
348
349 def create_player_weapon_stats(session=None, player=None, 
350         game=None, pgstat=None, player_events=None):
351     pwstats = []
352
353     for (key,value) in player_events.items():
354         matched = re.search("acc-(.*?)-cnt-fired", key)
355         if matched:
356             weapon_cd = matched.group(1)
357             pwstat = PlayerWeaponStat()
358             pwstat.player_id = player.player_id
359             pwstat.game_id = game.game_id
360             pwstat.player_game_stat_id = pgstat.player_game_stat_id
361             pwstat.weapon_cd = weapon_cd
362
363             if 'n' in player_events:
364                 pwstat.nick = player_events['n']
365             else:
366                 pwstat.nick = player_events['P']
367
368             if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
369                 pwstat.fired = int(round(float(
370                         player_events['acc-' + weapon_cd + '-cnt-fired'])))
371             if 'acc-' + weapon_cd + '-fired' in player_events:
372                 pwstat.max = int(round(float(
373                         player_events['acc-' + weapon_cd + '-fired'])))
374             if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
375                 pwstat.hit = int(round(float(
376                         player_events['acc-' + weapon_cd + '-cnt-hit'])))
377             if 'acc-' + weapon_cd + '-hit' in player_events:
378                 pwstat.actual = int(round(float(
379                         player_events['acc-' + weapon_cd + '-hit'])))
380             if 'acc-' + weapon_cd + '-frags' in player_events:
381                 pwstat.frags = int(round(float(
382                         player_events['acc-' + weapon_cd + '-frags'])))
383
384             session.add(pwstat)
385             pwstats.append(pwstat)
386
387     return pwstats
388
389
390 def parse_body(request):
391     # storage vars for the request body
392     game_meta = {}
393     player_events = {}
394     current_team = None
395     players = []
396     
397     log.debug(request.body)
398
399     for line in request.body.split('\n'):
400         try:
401             (key, value) = line.strip().split(' ', 1)
402     
403             if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
404                 game_meta[key] = value
405
406             if key == 'P':
407                 # if we were working on a player record already, append
408                 # it and work on a new one (only set team info)
409                 if len(player_events) != 0:
410                     players.append(player_events)
411                     player_events = {}
412     
413                 player_events[key] = value
414
415             if key == 'e':
416                 (subkey, subvalue) = value.split(' ', 1)
417                 player_events[subkey] = subvalue
418             if key == 'n':
419                 player_events[key] = value
420             if key == 't':
421                 player_events[key] = value
422         except:
423             # no key/value pair - move on to the next line
424             pass
425     
426     # add the last player we were working on
427     if len(player_events) > 0:
428         players.append(player_events)
429
430     return (game_meta, players)
431
432
433 def create_player_stats(session=None, player=None, game=None, 
434         player_events=None):
435     if 'joins' in player_events and 'matches' in player_events\
436             and 'scoreboardvalid' in player_events:
437                 pgstat = create_player_game_stat(session=session, 
438                         player=player, game=game, player_events=player_events)
439                 if not re.search('^bot#\d+$', player_events['P']):
440                         create_player_weapon_stats(session=session, 
441                             player=player, game=game, pgstat=pgstat,
442                             player_events=player_events)
443     
444
445 @view_config(renderer='stats_submit.mako')
446 def stats_submit(request):
447     try:
448         session = DBSession()
449
450         (game_meta, players) = parse_body(request)  
451     
452         # verify required metadata is present
453         if 'T' not in game_meta or\
454             'G' not in game_meta or\
455             'M' not in game_meta or\
456             'S' not in game_meta:
457             log.debug("Required game meta fields (T, G, M, or S) missing. "\
458                     "Can't continue.")
459             raise Exception("Required game meta fields (T, G, M, or S) missing.")
460     
461         has_real_players = False
462         for player_events in players:
463             if not player_events['P'].startswith('bot'):
464                 if 'joins' in player_events and 'matches' in player_events\
465                     and 'scoreboardvalid' in player_events:
466                     has_real_players = True
467
468         if not has_real_players:
469             raise Exception("No real players found. Stats ignored.")
470
471         server = get_or_create_server(session=session, name=game_meta['S'])
472         gmap = get_or_create_map(session=session, name=game_meta['M'])
473
474         if 'W' in game_meta:
475             winner = game_meta['W']
476         else:
477             winner = None
478
479         game = create_game(session=session, 
480                 start_dt=datetime.datetime(
481                     *time.gmtime(float(game_meta['T']))[:6]), 
482                 server_id=server.server_id, game_type_cd=game_meta['G'], 
483                 map_id=gmap.map_id, winner=winner)
484     
485         # find or create a record for each player
486         # and add stats for each if they were present at the end
487         # of the game
488         for player_events in players:
489             player = get_or_create_player(session=session, 
490                     hashkey=player_events['P'])
491             log.debug('Creating stats for %s' % player_events['P'])
492             create_player_stats(session=session, player=player, game=game, 
493                     player_events=player_events)
494     
495         session.commit()
496         log.debug('Success! Stats recorded.')
497         return Response('200 OK')
498     except Exception as e:
499         session.rollback()
500         raise e