X-Git-Url: http://de.git.xonotic.org/?a=blobdiff_plain;f=xonstat%2Fviews%2Fmain.py;h=3f76f1e56168ce52fe037db2c843656b61baf963;hb=f0f55403d0405bf14e035133c21832afd0ba05ce;hp=5a8bad4be70aead377dbf2d384af734b831fb4e1;hpb=678b131718d039ea912c8458a3db9264c8d9e883;p=xonotic%2Fxonstat.git diff --git a/xonstat/views/main.py b/xonstat/views/main.py index 5a8bad4..3f76f1e 100644 --- a/xonstat/views/main.py +++ b/xonstat/views/main.py @@ -1,15 +1,198 @@ import logging -import sqlalchemy.sql.functions as func -import sqlalchemy.sql.expression as expr from datetime import datetime, timedelta -from pyramid.response import Response -from xonstat.models import * -from xonstat.util import * + +from beaker.cache import cache_region +from sqlalchemy import text +from xonstat.models import DBSession, PlayerRank, ActivePlayer, ActiveServer, ActiveMap +from xonstat.views.helpers import RecentGame, recent_games_q log = logging.getLogger(__name__) + +@cache_region('hourly_term') +def summary_stats_data(scope="all"): + """ + Gets the summary stats (number of active players, the game type, and the number of games) + for a given scope. + + :param scope: The scope to fetch from the table. May be "all" or "day". + :return: list[tuple] + """ + sql = text("SELECT num_players, game_type_cd, num_games, create_dt refresh_dt " + "FROM summary_stats_mv " + "WHERE scope = :scope " + "ORDER BY sort_order ") + + try: + ss = DBSession.query("num_players", "game_type_cd", "num_games", "refresh_dt").\ + from_statement(sql).params(scope=scope).all() + + return ss + except Exception as e: + log.error(e) + return [] + + +def summary_stats_json(request): + scope = request.params.get("scope", "all") + if scope not in ["all", "day"]: + scope = "all" + + ss = summary_stats_data(scope) + + # default values + players = 0 + last_refreshed = "unknown" + games = [] + + if len(ss) > 0: + players = ss[0].num_players + last_refreshed = ss[0].refresh_dt.isoformat() + games = [{"game_type_cd": r.game_type_cd, "num_games": r.num_games} for r in ss] + + return { + "players": players, + "scope": scope, + "last_refreshed": last_refreshed, + "games": games, + } + + +@cache_region('hourly_term') +def summary_stats_string(scope="all"): + """ + Assembles the summary stats data into a readable line for direct inclusion in templates. + """ + try: + ss = summary_stats_data(scope) + + i = 1 + total_games = 0 + other_games = 0 + for row in ss: + # the number of players is constant in each row + total_players = row.num_players + + total_games += row.num_games + + # we can't show all game types on the single summary line, so any + # past the fifth one will get bundled in to an "other" count + if i > 5: + other_games += row.num_games + + i += 1 + + # don't send anything if we don't have any activity + if total_games == 0: + stat_line = None + else: + # This is ugly because we're doing template-like stuff within the + # view code. The alternative isn't any better, though: we would + # have to assemble the string inside the template by using a Python + # code block. For now I'll leave it like this since it is the lesser + # of two evils IMO. + # Also we need to hard-code the URL structure in here to allow caching, + # which also sucks. + in_paren = "; ".join(["{:2,d} {}".format( + g.num_games, + "{0}".format(g.game_type_cd) + ) for g in ss[:5]]) + + if other_games > 0: + in_paren += "; {:2,d} other".format(other_games) + + stat_line = "{:2,d} players and {:2,d} games ({})".format( + total_players, + total_games, + in_paren + ) + + except Exception as e: + stat_line = None + raise e + + return stat_line + + +@cache_region('hourly_term') +def get_ranks(game_type_cd): + """ + Gets a set number of the top-ranked people for the specified game_type_cd. + + The game_type_cd parameter is the type to fetch. Currently limited to + duel, dm, ctf, and tdm. + """ + # how many ranks we want to fetch + leaderboard_count = 10 + + # only a few game modes are actually ranked + if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm': + return None + + ranks = DBSession.query(PlayerRank).\ + filter(PlayerRank.game_type_cd==game_type_cd).\ + order_by(PlayerRank.rank).\ + limit(leaderboard_count).all() + + return ranks + + +@cache_region('hourly_term') +def get_top_players_by_time(limit=None, start=None): + """ + The top players by the amount of time played during a date range. + """ + q = DBSession.query(ActivePlayer) + + if start is not None: + q = q.filter(ActivePlayer.sort_order >= start) + + q = q.order_by(ActivePlayer.sort_order) + + if limit is not None: + q = q.limit(limit) + + return q.all() + + +@cache_region('hourly_term') +def get_top_servers_by_play_time(limit=None, start=None): + """ + The top servers by the cumulative amount of time played on them during a given interval. + """ + q = DBSession.query(ActiveServer) + + if start is not None: + q = q.filter(ActiveServer.sort_order >= start) + + q = q.order_by(ActiveServer.sort_order) + + if limit is not None: + q = q.limit(limit) + + return q.all() + + +@cache_region('hourly_term') +def get_top_maps_by_games(limit=None, start=None): + """ + The top maps by the number of games played during a date range. + """ + q = DBSession.query(ActiveMap) + + if start is not None: + q = q.filter(ActiveMap.sort_order >= start) + + q = q.order_by(ActiveMap.sort_order) + + if limit is not None: + q = q.limit(limit) + + return q.all() + + def _main_index_data(request): - try: + try: leaderboard_lifetime = int( request.registry.settings['xonstat.leaderboard_lifetime']) except: @@ -18,86 +201,41 @@ def _main_index_data(request): leaderboard_count = 10 recent_games_count = 20 - # top ranked duelers - duel_ranks = DBSession.query(PlayerRank.player_id, PlayerRank.nick, - PlayerRank.elo).\ - filter(PlayerRank.game_type_cd=='duel').\ - order_by(PlayerRank.rank).\ - limit(leaderboard_count).all() - - duel_ranks = [(player_id, html_colors(nick), elo) \ - for (player_id, nick, elo) in duel_ranks] - - # top ranked CTF-ers - ctf_ranks = DBSession.query(PlayerRank.player_id, PlayerRank.nick, - PlayerRank.elo).\ - filter(PlayerRank.game_type_cd=='ctf').\ - order_by(PlayerRank.rank).\ - limit(leaderboard_count).all() - - ctf_ranks = [(player_id, html_colors(nick), elo) \ - for (player_id, nick, elo) in ctf_ranks] + # summary statistics for the tagline + stat_line = summary_stats_string("all") + day_stat_line = summary_stats_string("day") - # top ranked DM-ers - dm_ranks = DBSession.query(PlayerRank.player_id, PlayerRank.nick, - PlayerRank.elo).\ - filter(PlayerRank.game_type_cd=='dm').\ - order_by(PlayerRank.rank).\ - limit(leaderboard_count).all() - dm_ranks = [(player_id, html_colors(nick), elo) \ - for (player_id, nick, elo) in dm_ranks] + # the three top ranks tables + ranks = [] + for gtc in ['duel', 'ctf', 'dm', 'tdm']: + rank = get_ranks(gtc) + if len(rank) != 0: + ranks.append(rank) right_now = datetime.utcnow() back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime) # top players by playing time - top_players = DBSession.query(Player.player_id, Player.nick, - func.sum(PlayerGameStat.alivetime)).\ - filter(Player.player_id == PlayerGameStat.player_id).\ - filter(Player.player_id > 2).\ - filter(expr.between(PlayerGameStat.create_dt, back_then, right_now)).\ - order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\ - group_by(Player.nick).\ - group_by(Player.player_id).limit(leaderboard_count).all() - - top_players = [(player_id, html_colors(nick), score) \ - for (player_id, nick, score) in top_players] - - # top servers by number of total players played - top_servers = DBSession.query(Server.server_id, Server.name, - func.count()).\ - filter(Game.server_id==Server.server_id).\ - filter(expr.between(Game.create_dt, back_then, right_now)).\ - order_by(expr.desc(func.count(Game.game_id))).\ - group_by(Server.server_id).\ - group_by(Server.name).limit(leaderboard_count).all() + top_players = get_top_players_by_time(10) + + # top servers by number of games + top_servers = get_top_servers_by_play_time(10) # top maps by total times played - top_maps = DBSession.query(Game.map_id, Map.name, - func.count()).\ - filter(Map.map_id==Game.map_id).\ - filter(expr.between(Game.create_dt, back_then, right_now)).\ - order_by(expr.desc(func.count())).\ - group_by(Game.map_id).\ - group_by(Map.name).limit(leaderboard_count).all() + top_maps = get_top_maps_by_games(10) # recent games played in descending order - recent_games = DBSession.query(Game, Server, Map, PlayerGameStat).\ - filter(Game.server_id==Server.server_id).\ - filter(Game.map_id==Map.map_id).\ - filter(PlayerGameStat.game_id==Game.game_id).\ - filter(PlayerGameStat.rank==1).\ - filter(expr.between(Game.create_dt, back_then, right_now)).\ - order_by(expr.desc(Game.start_dt)).limit(recent_games_count).all() + rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all() + recent_games = [RecentGame(row) for row in rgs] return {'top_players':top_players, 'top_servers':top_servers, 'top_maps':top_maps, 'recent_games':recent_games, - 'duel_ranks':duel_ranks, - 'ctf_ranks':ctf_ranks, - 'dm_ranks':dm_ranks, + 'ranks':ranks, + 'stat_line':stat_line, + 'day_stat_line':day_stat_line, } @@ -105,31 +243,71 @@ def main_index(request): """ Display the main page information. """ - mainindex_data = _main_index_data(request) + return _main_index_data(request) + + +def main_index_json(request): + """ + JSON output of the main page information. + """ + return [{'status':'not implemented'}] - # FIXME: code clone, should get these from _main_index_data - leaderboard_count = 10 - recent_games_count = 20 - for i in range(leaderboard_count-len(mainindex_data['duel_ranks'])): - mainindex_data['duel_ranks'].append(('-', '-', '-')) +def top_players_index(request): + try: + start = int(request.params.get('start', None)) + except: + start = None - for i in range(leaderboard_count-len(mainindex_data['ctf_ranks'])): - mainindex_data['ctf_ranks'].append(('-', '-', '-')) + top_players = get_top_players_by_time(20, start) - for i in range(leaderboard_count-len(mainindex_data['dm_ranks'])): - mainindex_data['dm_ranks'].append(('-', '-', '-')) + # building a query string + query = {} + if len(top_players) > 1: + query['start'] = top_players[-1].sort_order + 1 - for i in range(leaderboard_count-len(mainindex_data['top_players'])): - mainindex_data['top_players'].append(('-', '-', '-')) + return { + 'top_players':top_players, + 'query':query, + 'start':start, + } - for i in range(leaderboard_count-len(mainindex_data['top_servers'])): - mainindex_data['top_servers'].append(('-', '-', '-')) - for i in range(leaderboard_count-len(mainindex_data['top_maps'])): - mainindex_data['top_maps'].append(('-', '-', '-')) +def top_servers_index(request): + try: + start = int(request.params.get('start', None)) + except: + start = None - for i in range(recent_games_count-len(mainindex_data['recent_games'])): - mainindex_data['recent_games'].append(('-', '-', '-', '-')) + top_servers = get_top_servers_by_play_time(20, start) - return mainindex_data + # building a query string + query = {} + if len(top_servers) > 1: + query['start'] = top_servers[-1].sort_order + 1 + + return { + 'top_servers':top_servers, + 'query':query, + 'start':start, + } + + +def top_maps_index(request): + try: + start = int(request.params.get('start', None)) + except: + start = None + + top_maps = get_top_maps_by_games(20, start) + + # building a query string + query = {} + if len(top_maps) > 1: + query['start'] = top_maps[-1].sort_order + 1 + + return { + 'top_maps':top_maps, + 'query':query, + 'start':start, + }