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