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