3 from pyramid.response import Response
4 from pyramid.view import view_config
6 from xonstat.models import *
7 from sqlalchemy.orm.exc import MultipleResultsFound, NoResultFound
11 log = logging.getLogger(__name__)
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'}
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}
31 @view_config(renderer='player_info.mako')
32 def player_info(request):
33 player_id = request.matchdict['id']
35 player = DBSession.query(Player).filter_by(player_id=player_id).one()
36 recent_games = DBSession.query("game_id", "server_id", "server_name",
37 "map_id", "map_name").\
38 from_statement("select g.game_id, s.server_id, s.name as server_name, m.map_id, m.name as map_name "
39 "from player_game_stats gs, games g, servers s, maps m "
40 "where gs.player_id=:player_id "
41 "and gs.game_id = g.game_id "
42 "and g.server_id = s.server_id "
43 "and g.map_id = m.map_id "
44 "order by g.start_dt desc "
45 "limit 10 offset 1").\
46 params(player_id=player_id).all()
48 log.debug(recent_games)
49 except Exception as e:
52 return {'player':player, 'recent_games':recent_games}
55 ##########################################################################
56 # This is the game views area - only views pertaining to Xonotic
57 # games and their related information goes here
58 ##########################################################################
59 def game_info(request):
60 game_id = request.matchdict['id']
64 (start_dt, game_type_cd, server_id, server_name, map_id, map_name) = \
65 DBSession.query("start_dt", "game_type_cd", "server_id",
66 "server_name", "map_id", "map_name").\
67 from_statement("select g.start_dt, g.game_type_cd, "
68 "g.server_id, s.name as server_name, g.map_id, "
70 "from games g, servers s, maps m "
71 "where g.game_id = :game_id "
72 "and g.server_id = s.server_id "
73 "and g.map_id = m.map_id").\
74 params(game_id=game_id).one()
76 player_game_stats = DBSession.query(PlayerGameStat).\
77 from_statement("select * from player_game_stats "
78 "where game_id = :game_id "
79 "order by score desc").\
80 params(game_id=game_id).all()
81 except Exception as inst:
89 player_game_stats = None
91 return {'notfound':notfound,
93 'game_type_cd':game_type_cd,
94 'server_id':server_id,
95 'server_name':server_name,
98 'player_game_stats':player_game_stats}
101 ##########################################################################
102 # This is the server views area - only views pertaining to Xonotic
103 # servers and their related information goes here
104 ##########################################################################
105 def server_info(request):
106 server_id = request.matchdict['id']
108 server = DBSession.query(Server).filter_by(server_id=server_id).one()
109 recent_games = DBSession.query("game_id", "server_id", "server_name",
110 "map_id", "map_name").\
111 from_statement("select g.game_id, s.server_id, "
112 "s.name as server_name, m.map_id, m.name as map_name "
113 "from games g, servers s, maps m "
114 "where g.server_id=:server_id "
115 "and g.server_id = s.server_id "
116 "and g.map_id = m.map_id "
117 "order by g.start_dt desc "
118 "limit 10 offset 1").\
119 params(server_id=server_id).all()
121 except Exception as e:
124 return {'server':server,
125 'recent_games':recent_games}
128 ##########################################################################
129 # This is the map views area - only views pertaining to Xonotic
130 # maps and their related information goes here
131 ##########################################################################
132 def map_info(request):
133 map_id = request.matchdict['id']
135 gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
141 ##########################################################################
142 # This is the stats views area - only views pertaining to Xonotic
143 # statistics and its related information goes here
144 ##########################################################################
145 def get_or_create_server(session=None, name=None):
147 # find one by that name, if it exists
148 server = session.query(Server).filter_by(name=name).one()
149 log.debug("Found server id {0} with name {1}.".format(
150 server.server_id, server.name))
151 except NoResultFound, e:
152 server = Server(name=name)
155 log.debug("Created server id {0} with name {1}".format(
156 server.server_id, server.name))
157 except MultipleResultsFound, e:
158 # multiple found, so use the first one but warn
160 servers = session.query(Server).filter_by(name=name).order_by(
161 Server.server_id).all()
163 log.debug("Created server id {0} with name {1} but found \
165 server.server_id, server.name))
169 def get_or_create_map(session=None, name=None):
171 # find one by the name, if it exists
172 gmap = session.query(Map).filter_by(name=name).one()
173 log.debug("Found map id {0} with name {1}.".format(gmap.map_id,
175 except NoResultFound, e:
176 gmap = Map(name=name)
179 log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
181 except MultipleResultsFound, e:
182 # multiple found, so use the first one but warn
184 gmaps = session.query(Map).filter_by(name=name).order_by(
187 log.debug("Found map id {0} with name {1} but found \
188 multiple.".format(gmap.map_id, gmap.name))
192 def create_game(session=None, start_dt=None, game_type_cd=None,
193 server_id=None, map_id=None, winner=None):
194 game = Game(start_dt=start_dt, game_type_cd=game_type_cd,
195 server_id=server_id, map_id=map_id, winner=winner)
198 log.debug("Created game id {0} on server {1}, map {2} at time \
199 {3} and on map {4}".format(game.game_id,
200 server_id, map_id, start_dt, map_id))
204 # search for a player and if found, create a new one (w/ hashkey)
205 def get_or_create_player(session=None, hashkey=None):
207 if re.search('^bot#\d+$', hashkey):
208 player = session.query(Player).filter_by(player_id=1).one()
209 # if we have an untracked player
210 elif re.search('^player#\d+$', hashkey):
211 player = session.query(Player).filter_by(player_id=2).one()
212 # else it is a tracked player
214 # see if the player is already in the database
215 # if not, create one and the hashkey along with it
217 hashkey = session.query(Hashkey).filter_by(
218 hashkey=hashkey).one()
219 player = session.query(Player).filter_by(
220 player_id=hashkey.player_id).one()
221 log.debug("Found existing player {0} with hashkey {1}.".format(
222 player.player_id, hashkey.hashkey))
227 hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
229 log.debug("Created player {0} with hashkey {1}.".format(
230 player.player_id, hashkey.hashkey))
234 def create_player_game_stat(session=None, player=None,
235 game=None, player_events=None):
237 # in here setup default values (e.g. if game type is CTF then
238 # set kills=0, score=0, captures=0, pickups=0, fckills=0, etc
239 # TODO: use game's create date here instead of now()
240 pgstat = PlayerGameStat(create_dt=datetime.datetime.now())
242 # set player id from player record
243 pgstat.player_id = player.player_id
245 #set game id from game record
246 pgstat.game_id = game.game_id
248 # all games have a score
251 if game.game_type_cd == 'dm':
255 elif game.game_type_cd == 'ctf':
261 pgstat.carrier_frags = 0
263 for (key,value) in player_events.items():
264 if key == 'n': pgstat.nick = value
265 if key == 't': pgstat.team = value
266 if key == 'rank': pgstat.rank = value
267 if key == 'alivetime':
268 pgstat.alivetime = datetime.timedelta(seconds=int(round(float(value))))
269 if key == 'scoreboard-drops': pgstat.drops = value
270 if key == 'scoreboard-returns': pgstat.returns = value
271 if key == 'scoreboard-fckills': pgstat.carrier_frags = value
272 if key == 'scoreboard-pickups': pgstat.pickups = value
273 if key == 'scoreboard-caps': pgstat.captures = value
274 if key == 'scoreboard-score': pgstat.score = value
275 if key == 'scoreboard-deaths': pgstat.deaths = value
276 if key == 'scoreboard-kills': pgstat.kills = value
277 if key == 'scoreboard-suicides': pgstat.suicides = value
279 # check to see if we had a name, and if
280 # not use the name from the player id
281 if pgstat.nick == None:
282 pgstat.nick = player.nick
290 def create_player_weapon_stats(session=None, player=None,
291 game=None, player_events=None):
294 for (key,value) in player_events.items():
295 matched = re.search("acc-(.*?)-cnt-fired", key)
297 log.debug("Matched key: {0}".format(key))
298 weapon_cd = matched.group(1)
299 pwstat = PlayerWeaponStat()
300 pwstat.player_id = player.player_id
301 pwstat.game_id = game.game_id
302 pwstat.weapon_cd = weapon_cd
304 pwstat.max = int(player_events['acc-' + weapon_cd + '-fired'])
308 pwstat.actual = int(player_events['acc-' + weapon_cd + '-hit'])
312 pwstat.fired = int(player_events['acc-' + weapon_cd + '-cnt-fired'])
316 pwstat.hit = int(player_events['acc-' + weapon_cd + '-cnt-hit'])
320 pwstat.frags = int(player_events['acc-' + weapon_cd + '-frags'])
325 pwstats.append(pwstat)
330 def parse_body(request):
331 # storage vars for the request body
337 log.debug(request.body)
339 for line in request.body.split('\n'):
341 (key, value) = line.strip().split(' ', 1)
343 if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
344 game_meta[key] = value
350 # if we were working on a player record already, append
351 # it and work on a new one (only set team info)
352 if len(player_events) != 0:
353 players.append(player_events)
354 player_events = {'t':current_team}
356 player_events[key] = value
359 (subkey, subvalue) = value.split(' ', 1)
360 player_events[subkey] = subvalue
363 player_events[key] = value
365 # no key/value pair - move on to the next line
368 # add the last player we were working on
369 if len(player_events) > 0:
370 players.append(player_events)
372 return (game_meta, players)
375 @view_config(renderer='stats_submit.mako')
376 def stats_submit(request):
378 session = DBSession()
380 (game_meta, players) = parse_body(request)
382 # verify required metadata is present
383 if 'T' not in game_meta or\
384 'G' not in game_meta or\
385 'M' not in game_meta or\
386 'S' not in game_meta:
387 log.debug("Required game meta fields (T, G, M, or S) missing. "\
391 server = get_or_create_server(session=session, name=game_meta['S'])
392 gmap = get_or_create_map(session=session, name=game_meta['M'])
395 winner = game_meta['W']
399 # FIXME: don't use python now() here, convert from epoch T value
400 game = create_game(session=session, start_dt=datetime.datetime.now(),
401 server_id=server.server_id, game_type_cd=game_meta['G'],
402 map_id=gmap.map_id, winner=winner)
404 # find or create a record for each player
405 # and add stats for each if they were present at the end
407 has_real_players = False
408 for player_events in players:
409 if not player_events['P'].startswith('bot'):
410 has_real_players = True
411 player = get_or_create_player(session=session,
412 hashkey=player_events['P'])
413 if 'joins' in player_events and 'matches' in player_events\
414 and 'scoreboardvalid' in player_events:
415 pgstat = create_player_game_stat(session=session,
416 player=player, game=game, player_events=player_events)
417 #pwstats = create_player_weapon_stats(session=session,
418 #player=player, game=game, player_events=player_events)
422 log.debug('Success! Stats recorded.')
423 return Response('200 OK')
426 log.debug('No real players found. Stats ignored.')
427 return {'msg':'No real players found. Stats ignored.'}
428 except Exception as e: