]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views.py
Add recent games to server info view and template.
[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         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()
47
48         log.debug(recent_games)
49     except Exception as e:
50         player = None
51         recent_games = None
52     return {'player':player, 'recent_games':recent_games}
53
54
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']
61     try:
62         notfound = False
63
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, "
69                         "m.name as map_name "
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()
75
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:
82         notfound = True
83         start_dt = None
84         game_type_cd = None
85         server_id = None
86         server_name = None
87         map_id = None
88         map_name = None
89         player_game_stats = None
90
91     return {'notfound':notfound,
92             'start_dt':start_dt,
93             'game_type_cd':game_type_cd,
94             'server_id':server_id,
95             'server_name':server_name,
96             'map_id':map_id,
97             'map_name':map_name,
98             'player_game_stats':player_game_stats}
99
100
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']
107     try:
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()
120
121     except Exception as e:
122         server = None
123         recent_games = None
124     return {'server':server,
125             'recent_games':recent_games}
126
127
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']
134     try:
135         gmap = DBSession.query(Map).filter_by(map_id=map_id).one()
136     except:
137         gmap = None
138     return {'gmap':gmap}
139
140
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):
146     try:
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)
153         session.add(server)
154         session.flush()
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
159         log.debug(e)
160         servers = session.query(Server).filter_by(name=name).order_by(
161                 Server.server_id).all()
162         server = servers[0]
163         log.debug("Created server id {0} with name {1} but found \
164                 multiple".format(
165             server.server_id, server.name))
166
167     return server
168
169 def get_or_create_map(session=None, name=None):
170     try:
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, 
174             gmap.name))
175     except NoResultFound, e:
176         gmap = Map(name=name)
177         session.add(gmap)
178         session.flush()
179         log.debug("Created map id {0} with name {1}.".format(gmap.map_id,
180             gmap.name))
181     except MultipleResultsFound, e:
182         # multiple found, so use the first one but warn
183         log.debug(e)
184         gmaps = session.query(Map).filter_by(name=name).order_by(
185                 Map.map_id).all()
186         gmap = gmaps[0]
187         log.debug("Found map id {0} with name {1} but found \
188                 multiple.".format(gmap.map_id, gmap.name))
189
190     return gmap
191
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)
196     session.add(game)
197     session.flush()
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))
201
202     return game
203
204 # search for a player and if found, create a new one (w/ hashkey)
205 def get_or_create_player(session=None, hashkey=None):
206     # if we have a bot
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
213     else:
214         # see if the player is already in the database
215         # if not, create one and the hashkey along with it
216         try:
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))
223         except:
224             player = Player()
225             session.add(player)
226             session.flush()
227             hashkey = Hashkey(player_id=player.player_id, hashkey=hashkey)
228             session.add(hashkey)
229             log.debug("Created player {0} with hashkey {1}.".format(
230                 player.player_id, hashkey.hashkey))
231
232     return player
233
234 def create_player_game_stat(session=None, player=None, 
235         game=None, player_events=None):
236
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())
241
242     # set player id from player record
243     pgstat.player_id = player.player_id
244
245     #set game id from game record
246     pgstat.game_id = game.game_id
247
248     # all games have a score
249     pgstat.score = 0
250
251     if game.game_type_cd == 'dm':
252         pgstat.kills = 0
253         pgstat.deaths = 0
254         pgstat.suicides = 0
255     elif game.game_type_cd == 'ctf':
256         pgstat.kills = 0
257         pgstat.captures = 0
258         pgstat.pickups = 0
259         pgstat.drops = 0
260         pgstat.returns = 0
261         pgstat.carrier_frags = 0
262
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
278
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
283
284     session.add(pgstat)
285     session.flush()
286
287     return pgstat
288
289
290 def create_player_weapon_stats(session=None, player=None, 
291         game=None, player_events=None):
292     pwstats = []
293
294     for (key,value) in player_events.items():
295         matched = re.search("acc-(.*?)-cnt-fired", key)
296         if matched:
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
303             try:
304                 pwstat.max = int(player_events['acc-' + weapon_cd + '-fired'])
305             except:
306                 pwstat.max = 0
307             try:
308                 pwstat.actual = int(player_events['acc-' + weapon_cd + '-hit'])
309             except:
310                 pwstat.actual = 0
311             try:
312                 pwstat.fired = int(player_events['acc-' + weapon_cd + '-cnt-fired'])
313             except:
314                 pwstat.fired = 0
315             try:
316                 pwstat.hit = int(player_events['acc-' + weapon_cd + '-cnt-hit'])
317             except:
318                 pwstat.hit = 0
319             try:
320                 pwstat.frags = int(player_events['acc-' + weapon_cd + '-frags'])
321             except:
322                 pwstat.frags = 0
323
324             session.add(pwstat)
325             pwstats.append(pwstat)
326
327     return pwstats
328
329
330 def parse_body(request):
331     # storage vars for the request body
332     game_meta = {}
333     player_events = {}
334     current_team = None
335     players = []
336     
337     log.debug(request.body)
338
339     for line in request.body.split('\n'):
340         try:
341             (key, value) = line.strip().split(' ', 1)
342     
343             if key in 'V' 'T' 'G' 'M' 'S' 'C' 'R' 'W':
344                 game_meta[key] = value
345
346             if key == 't':
347                 current_team = value
348     
349             if key == 'P':
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}
355     
356                 player_events[key] = value
357     
358             if key == 'e':
359                 (subkey, subvalue) = value.split(' ', 1)
360                 player_events[subkey] = subvalue
361
362             if key == 'n':
363                 player_events[key] = value
364         except:
365             # no key/value pair - move on to the next line
366             pass
367     
368     # add the last player we were working on
369     if len(player_events) > 0:
370         players.append(player_events)
371
372     return (game_meta, players)
373
374
375 @view_config(renderer='stats_submit.mako')
376 def stats_submit(request):
377     try:
378         session = DBSession()
379
380         (game_meta, players) = parse_body(request)  
381     
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. "\
388                     "Can't continue.")
389             raise Exception
390     
391         server = get_or_create_server(session=session, name=game_meta['S'])
392         gmap = get_or_create_map(session=session, name=game_meta['M'])
393
394         if 'W' in game_meta:
395             winner = game_meta['W']
396         else:
397             winner = None
398
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)
403     
404         # find or create a record for each player
405         # and add stats for each if they were present at the end
406         # of the game
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)
419     
420         if has_real_players:
421             session.commit()
422             log.debug('Success! Stats recorded.')
423             return Response('200 OK')
424         else:
425             session.rollback()
426             log.debug('No real players found. Stats ignored.')
427             return {'msg':'No real players found. Stats ignored.'}
428     except Exception as e:
429         session.rollback()
430         raise e