4 from pyramid.response import Response
5 from pyramid.view import view_config
7 from xonstat.models import *
8 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
12 log = logging.getLogger(__name__)
14 ##########################################################################
15 # This is the main index - the entry point to the entire site
16 ##########################################################################
17 @view_config(renderer='index.jinja2')
18 def main_index(request):
19 log.debug("testing logging; entered MainHandler.index()")
20 return {'project':'xonstat'}
22 ##########################################################################
23 # This is the player views area - only views pertaining to Xonotic players
24 # and their related information goes here
25 ##########################################################################
26 @view_config(renderer='player_index.mako')
27 def player_index(request):
28 players = DBSession.query(Player)
29 log.debug("testing logging; entered PlayerHandler.index()")
30 return {'players':players}
32 @view_config(renderer='player_info.mako')
33 def player_info(request):
34 player_id = request.matchdict['id']
36 player = DBSession.query(Player).filter_by(player_id=player_id).one()
37 recent_games = DBSession.query("game_id", "server_id", "server_name",
38 "map_id", "map_name").\
39 from_statement("select g.game_id, s.server_id, s.name as server_name, m.map_id, m.name as map_name "
40 "from player_game_stats gs, games g, servers s, maps m "
41 "where gs.player_id=:player_id "
42 "and gs.game_id = g.game_id "
43 "and g.server_id = s.server_id "
44 "and g.map_id = m.map_id "
45 "order by g.start_dt desc "
46 "limit 10 offset 1").\
47 params(player_id=player_id).all()
49 log.debug(recent_games)
50 except Exception as e:
53 return {'player':player, 'recent_games':recent_games}
56 ##########################################################################
57 # This is the game views area - only views pertaining to Xonotic
58 # games and their related information goes here
59 ##########################################################################
60 def game_info(request):
61 game_id = request.matchdict['id']
65 (start_dt, game_type_cd, server_id, server_name, map_id, map_name) = \
66 DBSession.query("start_dt", "game_type_cd", "server_id",
67 "server_name", "map_id", "map_name").\
68 from_statement("select g.start_dt, g.game_type_cd, "
69 "g.server_id, s.name as server_name, g.map_id, "
71 "from games g, servers s, maps m "
72 "where g.game_id = :game_id "
73 "and g.server_id = s.server_id "
74 "and g.map_id = m.map_id").\
75 params(game_id=game_id).one()
77 player_game_stats = DBSession.query(PlayerGameStat).\
78 from_statement("select * from player_game_stats "
79 "where game_id = :game_id "
80 "order by score desc").\
81 params(game_id=game_id).all()
82 except Exception as inst:
90 player_game_stats = None
92 return {'notfound':notfound,
94 'game_type_cd':game_type_cd,
95 'server_id':server_id,
96 'server_name':server_name,
99 'player_game_stats':player_game_stats}
102 ##########################################################################
103 # This is the server views area - only views pertaining to Xonotic
104 # servers and their related information goes here
105 ##########################################################################
106 def server_info(request):
107 server_id = request.matchdict['id']
109 server = DBSession.query(Server).filter_by(server_id=server_id).one()
110 recent_games = DBSession.query("game_id", "server_id", "server_name",
111 "map_id", "map_name").\
112 from_statement("select g.game_id, s.server_id, "
113 "s.name as server_name, m.map_id, m.name as map_name "
114 "from games g, servers s, maps m "
115 "where g.server_id=:server_id "
116 "and g.server_id = s.server_id "
117 "and g.map_id = m.map_id "
118 "order by g.start_dt desc "
119 "limit 10 offset 1").\
120 params(server_id=server_id).all()
122 except Exception as e:
125 return {'server':server,
126 'recent_games':recent_games}
129 ##########################################################################
130 # This is the map views area - only views pertaining to Xonotic
131 # maps and their related information goes here
132 ##########################################################################
133 def map_info(request):
134 map_id = request.matchdict['id']
136 gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
142 ##########################################################################
143 # This is the stats views area - only views pertaining to Xonotic
144 # statistics and its related information goes here
145 ##########################################################################
146 def get_or_create_server(session=None, name=None):
148 # find one by that name, if it exists
149 server = session.query(Server).filter_by(name=name).one()
150 log.debug("Found server id {0} with name {1}.".format(
151 server.server_id, server.name))
152 except NoResultFound, e:
153 server = Server(name=name)
156 log.debug("Created server id {0} with name {1}".format(
157 server.server_id, server.name))
158 except MultipleResultsFound, e:
159 # multiple found, so use the first one but warn
161 servers = session.query(Server).filter_by(name=name).order_by(
162 Server.server_id).all()
164 log.debug("Created server id {0} with name {1} but found \
166 server.server_id, server.name))
170 def get_or_create_map(session=None, name=None):
172 # find one by the name, if it exists
173 gmap = session.query(Map).filter_by(name=name).one()
174 log.debug("Found map id {0} with name {1}.".format(gmap.map_id,
176 except NoResultFound, e:
177 gmap = Map(name=name)
180 log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
182 except MultipleResultsFound, e:
183 # multiple found, so use the first one but warn
185 gmaps = session.query(Map).filter_by(name=name).order_by(
188 log.debug("Found map id {0} with name {1} but found \
189 multiple.".format(gmap.map_id, gmap.name))
193 def create_game(session=None, start_dt=None, game_type_cd=None,
194 server_id=None, map_id=None, winner=None):
195 game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
196 server_id=server_id, map_id=map_id, winner=winner)
199 log.debug("Created game id {0} on server {1}, map {2} at time \
200 {3} and on map {4}".format(game.game_id,
201 server_id, map_id, start_dt, map_id))
205 # search for a player and if found, create a new one (w/ hashkey)
206 def get_or_create_player(session=None, hashkey=None):
208 if re.search('^bot#\d+$', hashkey):
209 player = session.query(Player).filter_by(player_id=1).one()
210 # if we have an untracked player
211 elif re.search('^player#\d+$', hashkey):
212 player = session.query(Player).filter_by(player_id=2).one()
213 # else it is a tracked player
215 # see if the player is already in the database
216 # if not, create one and the hashkey along with it
218 hashkey = session.query(Hashkey).filter_by(
219 hashkey=hashkey).one()
220 player = session.query(Player).filter_by(
221 player_id=hashkey.player_id).one()
222 log.debug("Found existing player {0} with hashkey {1}.".format(
223 player.player_id, hashkey.hashkey))
228 hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
230 log.debug("Created player {0} with hashkey {1}.".format(
231 player.player_id, hashkey.hashkey))
235 def create_player_game_stat(session=None, player=None,
236 game=None, player_events=None):
238 # in here setup default values (e.g. if game type is CTF then
239 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
240 # TODO: use game's create date here instead of now()
241 pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
243 # set player id from player record
244 pgstat.player_id = player.player_id
246 #set game id from game record
247 pgstat.game_id = game.game_id
249 # all games have a score
252 if game.game_type_cd == 'dm':
256 elif game.game_type_cd == 'ctf':
262 pgstat.carrier_frags = 0
264 for (key,value) in player_events.items():
265 if key == 'n': pgstat.nick = value
266 if key == 't': pgstat.team = value
267 if key == 'rank': pgstat.rank = value
268 if key == 'alivetime':
269 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
270 if key == 'scoreboard-drops': pgstat.drops = value
271 if key == 'scoreboard-returns': pgstat.returns = value
272 if key == 'scoreboard-fckills': pgstat.carrier_frags = value
273 if key == 'scoreboard-pickups': pgstat.pickups = value
274 if key == 'scoreboard-caps': pgstat.captures = value
275 if key == 'scoreboard-score': pgstat.score = value
276 if key == 'scoreboard-deaths': pgstat.deaths = value
277 if key == 'scoreboard-kills': pgstat.kills = value
278 if key == 'scoreboard-suicides': pgstat.suicides = value
280 # check to see if we had a name, and if
281 # not use the name from the player id
282 if pgstat.nick == None:
283 pgstat.nick = player.nick
291 def create_player_weapon_stats(session=None, player=None,
292 game=None, player_events=None):
295 for (key,value) in player_events.items():
296 matched = re.search("acc-(.*?)-cnt-fired", key)
298 log.debug("Matched key: {0}".format(key))
299 weapon_cd = matched.group(1)
300 pwstat = PlayerWeaponStat()
301 pwstat.player_id = player.player_id
302 pwstat.game_id = game.game_id
303 pwstat.weapon_cd = weapon_cd
305 pwstat.max = int(player_events['acc-' + weapon_cd + '-fired'])
309 pwstat.actual = int(player_events['acc-' + weapon_cd + '-hit'])
313 pwstat.fired = int(player_events['acc-' + weapon_cd + '-cnt-fired'])
317 pwstat.hit = int(player_events['acc-' + weapon_cd + '-cnt-hit'])
321 pwstat.frags = int(player_events['acc-' + weapon_cd + '-frags'])
326 pwstats.append(pwstat)
331 def parse_body(request):
332 # storage vars for the request body
338 log.debug(request.body)
340 for line in request.body.split('\n'):
342 (key, value) = line.strip().split(' ', 1)
344 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
345 game_meta[key] = value
351 # if we were working on a player record already, append
352 # it and work on a new one (only set team info)
353 if len(player_events) != 0:
354 players.append(player_events)
355 player_events = {'t':current_team}
357 player_events[key] = value
360 (subkey, subvalue) = value.split(' ', 1)
361 player_events[subkey] = subvalue
364 player_events[key] = value
366 # no key/value pair - move on to the next line
369 # add the last player we were working on
370 if len(player_events) > 0:
371 players.append(player_events)
373 return (game_meta, players)
376 @view_config(renderer='stats_submit.mako')
377 def stats_submit(request):
379 session = DBSession()
381 (game_meta, players) = parse_body(request)
383 # verify required metadata is present
384 if 'T' not in game_meta or\
385 'G' not in game_meta or\
386 'M' not in game_meta or\
387 'S' not in game_meta:
388 log.debug("Required game meta fields (T, G, M, or S) missing. "\
390 raise Exception("Required game meta fields (T, G, M, or S) missing.")
392 has_real_players = False
393 for player_events in players:
394 if not player_events['P'].startswith('bot'):
395 has_real_players = True
397 if not has_real_players:
398 raise Exception("No real players found. Stats ignored.")
400 server = get_or_create_server(session=session, name=game_meta['S'])
401 gmap = get_or_create_map(session=session, name=game_meta['M'])
404 winner = game_meta['W']
408 game = create_game(session=session,
409 start_dt=datetime.datetime(
410 *time.gmtime(float(game_meta['T']))[:6]),
411 server_id=server.server_id, game_type_cd=game_meta['G'],
412 map_id=gmap.map_id, winner=winner)
414 # find or create a record for each player
415 # and add stats for each if they were present at the end
417 for player_events in players:
418 player = get_or_create_player(session=session,
419 hashkey=player_events['P'])
420 if 'joins' in player_events and 'matches' in player_events\
421 and 'scoreboardvalid' in player_events:
422 pgstat = create_player_game_stat(session=session,
423 player=player, game=game, player_events=player_events)
424 #pwstats = create_player_weapon_stats(session=session,
425 #player=player, game=game, player_events=player_events)
428 log.debug('Success! Stats recorded.')
429 return Response('200 OK')
430 except Exception as e: