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__)
-def main_index(request):
- try:
+
+@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,
+ "<a href='/games?type={0}'>{0}</a>".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:
leaderboard_lifetime = int(
request.registry.settings['xonstat.leaderboard_lifetime'])
except:
leaderboard_count = 10
recent_games_count = 20
- # top ranked duelers
- duel_ranks = DBSession.query(Player.player_id, Player.nick, PlayerElo.elo).\
- filter(Player.player_id==PlayerElo.player_id).\
- filter(PlayerElo.game_type_cd=='duel').\
- filter(PlayerElo.games >= 32).\
- order_by(expr.desc(PlayerElo.elo)).all()[0:10]
+ # summary statistics for the tagline
+ stat_line = summary_stats_string("all")
+ day_stat_line = summary_stats_string("day")
- duel_ranks = [(player_id, html_colors(nick), elo) \
- for (player_id, nick, elo) in duel_ranks]
- for i in range(leaderboard_count-len(duel_ranks)):
- duel_ranks.append(('-', '-', '-'))
+ # the three top ranks tables
+ ranks = []
+ for gtc in ['duel', 'ctf', 'dm', 'tdm']:
+ rank = get_ranks(gtc)
+ if len(rank) != 0:
+ ranks.append(rank)
- # top ranked CTF-ers
- ctf_ranks = DBSession.query(Player.player_id, Player.nick, PlayerElo.elo).\
- filter(Player.player_id==PlayerElo.player_id).\
- filter(PlayerElo.game_type_cd=='ctf').\
- filter(PlayerElo.games >= 32).\
- order_by(expr.desc(PlayerElo.elo)).all()[0:10]
+ right_now = datetime.utcnow()
+ back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
- ctf_ranks = [(player_id, html_colors(nick), elo) \
- for (player_id, nick, elo) in ctf_ranks]
+ # top players by playing time
+ top_players = get_top_players_by_time(10)
- for i in range(leaderboard_count-len(ctf_ranks)):
- ctf_ranks.append(('-', '-', '-'))
+ # top servers by number of games
+ top_servers = get_top_servers_by_play_time(10)
- # top ranked DM-ers
- dm_ranks = DBSession.query(Player.player_id, Player.nick, PlayerElo.elo).\
- filter(Player.player_id==PlayerElo.player_id).\
- filter(PlayerElo.game_type_cd=='dm').\
- filter(PlayerElo.games >= 32).\
- order_by(expr.desc(PlayerElo.elo)).all()[0:10]
+ # top maps by total times played
+ top_maps = get_top_maps_by_games(10)
- dm_ranks = [(player_id, html_colors(nick), elo) \
- for (player_id, nick, elo) in dm_ranks]
+ # recent games played in descending order
+ rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
+ recent_games = [RecentGame(row) for row in rgs]
- for i in range(leaderboard_count-len(dm_ranks)):
- dm_ranks.append(('-', '-', '-'))
+ return {'top_players':top_players,
+ 'top_servers':top_servers,
+ 'top_maps':top_maps,
+ 'recent_games':recent_games,
+ 'ranks':ranks,
+ 'stat_line':stat_line,
+ 'day_stat_line':day_stat_line,
+ }
- # 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(PlayerGameStat.create_dt >
- (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
- order_by(expr.desc(func.sum(PlayerGameStat.alivetime))).\
- group_by(Player.nick).\
- group_by(Player.player_id).all()[0:10]
-
- top_players = [(player_id, html_colors(nick), score) \
- for (player_id, nick, score) in top_players]
-
- for i in range(leaderboard_count-len(top_players)):
- top_players.append(('-', '-', '-'))
-
- # 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(Game.create_dt >
- (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
- order_by(expr.desc(func.count(Game.game_id))).\
- group_by(Server.server_id).\
- group_by(Server.name).all()[0:10]
-
- for i in range(leaderboard_count-len(top_servers)):
- top_servers.append(('-', '-', '-'))
- # 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(Game.create_dt >
- (datetime.utcnow() - timedelta(days=leaderboard_lifetime))).\
- order_by(expr.desc(func.count())).\
- group_by(Game.map_id).\
- group_by(Map.name).all()[0:10]
-
- for i in range(leaderboard_count-len(top_maps)):
- top_maps.append(('-', '-', '-'))
+def main_index(request):
+ """
+ Display the main page information.
+ """
+ return _main_index_data(request)
- # 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).\
- order_by(expr.desc(Game.start_dt)).all()[0:recent_games_count]
- for i in range(recent_games_count-len(recent_games)):
- recent_games.append(('-', '-', '-', '-'))
+def main_index_json(request):
+ """
+ JSON output of the main page information.
+ """
+ return [{'status':'not implemented'}]
- return {'top_players':top_players,
+
+def top_players_index(request):
+ try:
+ start = int(request.params.get('start', None))
+ except:
+ start = None
+
+ top_players = get_top_players_by_time(20, start)
+
+ # building a query string
+ query = {}
+ if len(top_players) > 1:
+ query['start'] = top_players[-1].sort_order + 1
+
+ return {
+ 'top_players':top_players,
+ 'query':query,
+ 'start':start,
+ }
+
+
+def top_servers_index(request):
+ try:
+ start = int(request.params.get('start', None))
+ except:
+ start = None
+
+ top_servers = get_top_servers_by_play_time(20, start)
+
+ # 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,
- 'recent_games':recent_games,
- 'duel_ranks':duel_ranks,
- 'ctf_ranks':ctf_ranks,
- 'dm_ranks':dm_ranks,
+ 'query':query,
+ 'start':start,
}