5 from pyramid.response import Response
6 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
7 from sqlalchemy import desc
8 from webhelpers.paginate import Page, PageURL
9 from xonstat.models import *
10 from xonstat.util import page_url
12 log = logging.getLogger(__name__)
14 ##########################################################################
15 def main_index(request):
17 This is the main entry point to the entire site.
19 log.debug("testing logging; entered MainHandler.index()")
20 return {'project':'xonstat'}
23 ##########################################################################
24 def player_index(request):
26 Provides a list of all the current players.
28 players = DBSession.query(Player)
30 log.debug("testing logging; entered PlayerHandler.index()")
31 return {'players':players}
33 def player_info(request):
35 Provides detailed information on a specific player
37 player_id = request.matchdict['id']
39 player = DBSession.query(Player).filter_by(player_id=player_id).one()
41 weapon_stats = DBSession.query("descr", "actual_total",
42 "max_total", "hit_total", "fired_total", "frags_total").\
44 "select cw.descr, sum(actual) actual_total, "
45 "sum(max) max_total, sum(hit) hit_total, "
46 "sum(fired) fired_total, sum(frags) frags_total "
47 "from xonstat.player_weapon_stats ws, xonstat.cd_weapon cw "
48 "where ws.weapon_cd = cw.weapon_cd "
49 "and player_id = :player_id "
52 ).params(player_id=player_id).all()
54 log.debug(weapon_stats)
56 recent_games = DBSession.query(PlayerGameStat, Game, Server, Map).\
57 filter(PlayerGameStat.player_id == player_id).\
58 filter(PlayerGameStat.game_id == Game.game_id).\
59 filter(Game.server_id == Server.server_id).\
60 filter(Game.map_id == Map.map_id).\
61 order_by(Game.game_id.desc())[0:10]
63 except Exception as e:
67 return {'player':player,
68 'recent_games':recent_games,
69 'weapon_stats':weapon_stats}
72 def player_game_index(request):
74 Provides an index of the games in which a particular
75 player was involved. This is ordered by game_id, with
76 the most recent game_ids first. Paginated.
78 player_id = request.matchdict['player_id']
80 if 'page' in request.matchdict:
81 current_page = request.matchdict['page']
86 player = DBSession.query(Player).filter_by(player_id=player_id).one()
88 games_q = DBSession.query(PlayerGameStat, Game, Server, Map).\
89 filter(PlayerGameStat.player_id == player_id).\
90 filter(PlayerGameStat.game_id == Game.game_id).\
91 filter(Game.server_id == Server.server_id).\
92 filter(Game.map_id == Map.map_id).\
93 order_by(Game.game_id.desc())
95 games = Page(games_q, current_page, url=page_url)
98 except Exception as e:
103 return {'player':player,
107 def player_weapon_stats(request):
109 List the accuracy statistics for the given player_id in a particular
112 game_id = request.matchdict['game_id']
113 pgstat_id = request.matchdict['pgstat_id']
115 pwstats = DBSession.query(PlayerWeaponStat, Weapon).\
116 filter(PlayerWeaponStat.weapon_cd==Weapon.weapon_cd).\
117 filter_by(game_id=game_id).\
118 filter_by(player_game_stat_id=pgstat_id).\
119 order_by(Weapon.descr).\
122 pgstat = DBSession.query(PlayerGameStat).\
123 filter_by(player_game_stat_id=pgstat_id).one()
125 game = DBSession.query(Game).filter_by(game_id=game_id).one()
131 except Exception as e:
136 return {'pwstats':pwstats, 'pgstat':pgstat, 'game':game}
139 ##########################################################################
140 def game_index(request):
142 Provides a list of current games, with the associated game stats.
143 These games are ordered by game_id, with the most current ones first.
146 if 'page' in request.matchdict:
147 current_page = request.matchdict['page']
151 games_q = DBSession.query(Game, Server, Map).\
152 filter(Game.server_id == Server.server_id).\
153 filter(Game.map_id == Map.map_id).\
154 order_by(Game.game_id.desc())
156 games = Page(games_q, current_page, url=page_url)
159 for (game, server, map) in games:
160 pgstats[game.game_id] = DBSession.query(PlayerGameStat).\
161 filter(PlayerGameStat.game_id == game.game_id).\
162 order_by(PlayerGameStat.rank).\
163 order_by(PlayerGameStat.score).all()
165 return {'games':games,
169 def game_info(request):
171 List the game stats (scoreboard) for a particular game. Paginated.
173 game_id = request.matchdict['id']
177 (start_dt, game_type_cd, server_id, server_name, map_id, map_name) = \
178 DBSession.query("start_dt", "game_type_cd", "server_id",
179 "server_name", "map_id", "map_name").\
180 from_statement("select g.start_dt, g.game_type_cd, "
181 "g.server_id, s.name as server_name, g.map_id, "
182 "m.name as map_name "
183 "from games g, servers s, maps m "
184 "where g.game_id = :game_id "
185 "and g.server_id = s.server_id "
186 "and g.map_id = m.map_id").\
187 params(game_id=game_id).one()
189 player_game_stats = DBSession.query(PlayerGameStat).\
190 from_statement("select * from player_game_stats "
191 "where game_id = :game_id "
192 "order by score desc").\
193 params(game_id=game_id).all()
194 except Exception as inst:
202 player_game_stats = None
204 return {'notfound':notfound,
206 'game_type_cd':game_type_cd,
207 'server_id':server_id,
208 'server_name':server_name,
211 'player_game_stats':player_game_stats}
214 ##########################################################################
215 def server_info(request):
217 List the stored information about a given server.
219 server_id = request.matchdict['id']
221 server = DBSession.query(Server).filter_by(server_id=server_id).one()
222 recent_games = DBSession.query(Game, Server, Map).\
223 filter(Game.server_id == server_id).\
224 filter(Game.server_id == Server.server_id).\
225 filter(Game.map_id == Map.map_id).\
226 order_by(Game.game_id.desc())[0:10]
228 except Exception as e:
231 return {'server':server,
232 'recent_games':recent_games}
235 def server_game_index(request):
237 List the games played on a given server. Paginated.
239 server_id = request.matchdict['server_id']
240 current_page = request.matchdict['page']
243 server = DBSession.query(Server).filter_by(server_id=server_id).one()
245 games_q = DBSession.query(Game, Server, Map).\
246 filter(Game.server_id == server_id).\
247 filter(Game.server_id == Server.server_id).\
248 filter(Game.map_id == Map.map_id).\
249 order_by(Game.game_id.desc())
251 games = Page(games_q, current_page, url=page_url)
252 except Exception as e:
257 return {'games':games,
261 ##########################################################################
262 def map_info(request):
264 List the information stored about a given map.
266 map_id = request.matchdict['id']
268 gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
274 ##########################################################################
275 def get_or_create_server(session=None, name=None):
277 Find a server by name or create one if not found. Parameters:
279 session - SQLAlchemy database session factory
280 name - server name of the server to be found or created
283 # find one by that name, if it exists
284 server = session.query(Server).filter_by(name=name).one()
285 log.debug("Found server id {0} with name {1}.".format(
286 server.server_id, server.name))
287 except NoResultFound, e:
288 server = Server(name=name)
291 log.debug("Created server id {0} with name {1}".format(
292 server.server_id, server.name))
293 except MultipleResultsFound, e:
294 # multiple found, so use the first one but warn
296 servers = session.query(Server).filter_by(name=name).order_by(
297 Server.server_id).all()
299 log.debug("Created server id {0} with name {1} but found \
301 server.server_id, server.name))
305 def get_or_create_map(session=None, name=None):
307 Find a map by name or create one if not found. Parameters:
309 session - SQLAlchemy database session factory
310 name - map name of the map to be found or created
313 # find one by the name, if it exists
314 gmap = session.query(Map).filter_by(name=name).one()
315 log.debug("Found map id {0} with name {1}.".format(gmap.map_id,
317 except NoResultFound, e:
318 gmap = Map(name=name)
321 log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
323 except MultipleResultsFound, e:
324 # multiple found, so use the first one but warn
326 gmaps = session.query(Map).filter_by(name=name).order_by(
329 log.debug("Found map id {0} with name {1} but found \
330 multiple.".format(gmap.map_id, gmap.name))
335 def create_game(session=None, start_dt=None, game_type_cd=None,
336 server_id=None, map_id=None, winner=None):
338 Creates a game. Parameters:
340 session - SQLAlchemy database session factory
341 start_dt - when the game started (datetime object)
342 game_type_cd - the game type of the game being played
343 server_id - server identifier of the server hosting the game
344 map_id - map on which the game was played
345 winner - the team id of the team that won
348 game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
349 server_id=server_id, map_id=map_id, winner=winner)
352 log.debug("Created game id {0} on server {1}, map {2} at time \
353 {3} and on map {4}".format(game.game_id,
354 server_id, map_id, start_dt, map_id))
359 def get_or_create_player(session=None, hashkey=None, nick=None):
361 Finds a player by hashkey or creates a new one (along with a
362 corresponding hashkey entry. Parameters:
364 session - SQLAlchemy database session factory
365 hashkey - hashkey of the player to be found or created
366 nick - nick of the player (in case of a first time create)
369 if re.search('^bot#\d+$', hashkey):
370 player = session.query(Player).filter_by(player_id=1).one()
371 # if we have an untracked player
372 elif re.search('^player#\d+$', hashkey):
373 player = session.query(Player).filter_by(player_id=2).one()
374 # else it is a tracked player
376 # see if the player is already in the database
377 # if not, create one and the hashkey along with it
379 hashkey = session.query(Hashkey).filter_by(
380 hashkey=hashkey).one()
381 player = session.query(Player).filter_by(
382 player_id=hashkey.player_id).one()
383 log.debug("Found existing player {0} with hashkey {1}.".format(
384 player.player_id, hashkey.hashkey))
393 hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
395 log.debug("Created player {0} with hashkey {1}.".format(
396 player.player_id, hashkey.hashkey))
400 def create_player_game_stat(session=None, player=None,
401 game=None, player_events=None):
403 Creates game statistics for a given player in a given game. Parameters:
405 session - SQLAlchemy session factory
406 player - Player record of the player who owns the stats
407 game - Game record for the game to which the stats pertain
408 player_events - dictionary for the actual stats that need to be transformed
411 # in here setup default values (e.g. if game type is CTF then
412 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
413 # TODO: use game's create date here instead of now()
414 pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
416 # set player id from player record
417 pgstat.player_id = player.player_id
419 #set game id from game record
420 pgstat.game_id = game.game_id
422 # all games have a score
425 if game.game_type_cd == 'dm':
429 elif game.game_type_cd == 'ctf':
435 pgstat.carrier_frags = 0
437 for (key,value) in player_events.items():
438 if key == 'n': pgstat.nick = value
439 if key == 't': pgstat.team = value
440 if key == 'rank': pgstat.rank = value
441 if key == 'alivetime':
442 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
443 if key == 'scoreboard-drops': pgstat.drops = value
444 if key == 'scoreboard-returns': pgstat.returns = value
445 if key == 'scoreboard-fckills': pgstat.carrier_frags = value
446 if key == 'scoreboard-pickups': pgstat.pickups = value
447 if key == 'scoreboard-caps': pgstat.captures = value
448 if key == 'scoreboard-score': pgstat.score = value
449 if key == 'scoreboard-deaths': pgstat.deaths = value
450 if key == 'scoreboard-kills': pgstat.kills = value
451 if key == 'scoreboard-suicides': pgstat.suicides = value
453 # check to see if we had a name, and if
454 # not use the name from the player id
455 if pgstat.nick == None:
456 pgstat.nick = player.nick
464 def create_player_weapon_stats(session=None, player=None,
465 game=None, pgstat=None, player_events=None):
467 Creates accuracy records for each weapon used by a given player in a
468 given game. Parameters:
470 session - SQLAlchemy session factory object
471 player - Player record who owns the weapon stats
472 game - Game record in which the stats were created
473 pgstat - Corresponding PlayerGameStat record for these weapon stats
474 player_events - dictionary containing the raw weapon values that need to be
479 for (key,value) in player_events.items():
480 matched = re.search("acc-(.*?)-cnt-fired", key)
482 weapon_cd = matched.group(1)
483 pwstat = PlayerWeaponStat()
484 pwstat.player_id = player.player_id
485 pwstat.game_id = game.game_id
486 pwstat.player_game_stat_id = pgstat.player_game_stat_id
487 pwstat.weapon_cd = weapon_cd
489 if 'n' in player_events:
490 pwstat.nick = player_events['n']
492 pwstat.nick = player_events['P']
494 if 'acc-' + weapon_cd + '-cnt-fired' in player_events:
495 pwstat.fired = int(round(float(
496 player_events['acc-' + weapon_cd + '-cnt-fired'])))
497 if 'acc-' + weapon_cd + '-fired' in player_events:
498 pwstat.max = int(round(float(
499 player_events['acc-' + weapon_cd + '-fired'])))
500 if 'acc-' + weapon_cd + '-cnt-hit' in player_events:
501 pwstat.hit = int(round(float(
502 player_events['acc-' + weapon_cd + '-cnt-hit'])))
503 if 'acc-' + weapon_cd + '-hit' in player_events:
504 pwstat.actual = int(round(float(
505 player_events['acc-' + weapon_cd + '-hit'])))
506 if 'acc-' + weapon_cd + '-frags' in player_events:
507 pwstat.frags = int(round(float(
508 player_events['acc-' + weapon_cd + '-frags'])))
511 pwstats.append(pwstat)
516 def parse_body(request):
518 Parses the POST request body for a stats submission
520 # storage vars for the request body
526 log.debug(request.body)
528 for line in request.body.split('\n'):
530 (key, value) = line.strip().split(' ', 1)
532 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
533 game_meta[key] = value
536 # if we were working on a player record already, append
537 # it and work on a new one (only set team info)
538 if len(player_events) != 0:
539 players.append(player_events)
542 player_events[key] = value
545 (subkey, subvalue) = value.split(' ', 1)
546 player_events[subkey] = subvalue
548 player_events[key] = value
550 player_events[key] = value
552 # no key/value pair - move on to the next line
555 # add the last player we were working on
556 if len(player_events) > 0:
557 players.append(player_events)
559 return (game_meta, players)
562 def create_player_stats(session=None, player=None, game=None,
565 Creates player game and weapon stats according to what type of player
567 if 'joins' in player_events and 'matches' in player_events\
568 and 'scoreboardvalid' in player_events:
569 pgstat = create_player_game_stat(session=session,
570 player=player, game=game, player_events=player_events)
571 if not re.search('^bot#\d+$', player_events['P']):
572 create_player_weapon_stats(session=session,
573 player=player, game=game, pgstat=pgstat,
574 player_events=player_events)
577 def stats_submit(request):
579 Entry handler for POST stats submissions.
582 session = DBSession()
584 (game_meta, players) = parse_body(request)
586 # verify required metadata is present
587 if 'T' not in game_meta or\
588 'G' not in game_meta or\
589 'M' not in game_meta or\
590 'S' not in game_meta:
591 log.debug("Required game meta fields (T, G, M, or S) missing. "\
593 raise Exception("Required game meta fields (T, G, M, or S) missing.")
595 has_real_players = False
596 for player_events in players:
597 if not player_events['P'].startswith('bot'):
598 if 'joins' in player_events and 'matches' in player_events\
599 and 'scoreboardvalid' in player_events:
600 has_real_players = True
602 if not has_real_players:
603 raise Exception("No real players found. Stats ignored.")
605 server = get_or_create_server(session=session, name=game_meta['S'])
606 gmap = get_or_create_map(session=session, name=game_meta['M'])
609 winner = game_meta['W']
613 game = create_game(session=session,
614 start_dt=datetime.datetime(
615 *time.gmtime(float(game_meta['T']))[:6]),
616 server_id=server.server_id, game_type_cd=game_meta['G'],
617 map_id=gmap.map_id, winner=winner)
619 # find or create a record for each player
620 # and add stats for each if they were present at the end
622 for player_events in players:
623 if 'n' in player_events:
624 nick = player_events['n']
628 player = get_or_create_player(session=session,
629 hashkey=player_events['P'], nick=nick)
630 log.debug('Creating stats for %s' % player_events['P'])
631 create_player_stats(session=session, player=player, game=game,
632 player_events=player_events)
635 log.debug('Success! Stats recorded.')
636 return Response('200 OK')
637 except Exception as e: