]> de.git.xonotic.org Git - xonotic/xonstat.git/blobdiff - xonstat/views/main.py
Remove more refs to sqlahelper.
[xonotic/xonstat.git] / xonstat / views / main.py
old mode 100755 (executable)
new mode 100644 (file)
index 2e59f3f..3f76f1e
 import logging
-import sqlalchemy.sql.functions as func
-from pyramid.response import Response
-from xonstat.models import *
+from datetime import datetime, timedelta
+
+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):
-    # top players by score
-    top_players = DBSession.query(Player.player_id, Player.nick, 
-            func.sum(PlayerGameStat.score)).\
-            filter(Player.player_id == PlayerGameStat.player_id).\
-            filter(Player.player_id > 2).\
-            order_by(func.sum(PlayerGameStat.score)).\
-            group_by(Player.nick).\
-            group_by(Player.player_id).all()[0:10]
-
-    # top servers by number of total players played
-    top_servers = DBSession.query(Server.server_id, Server.name, 
-            func.count(Game.game_id)).\
-            filter(Game.server_id==Server.server_id).\
-            order_by(func.count(Game.game_id)).\
-            group_by(Server.server_id).\
-            group_by(Server.name).all()[0:10]
+
+@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_lifetime = 30
+
+    leaderboard_count = 10
+    recent_games_count = 20
+
+    # summary statistics for the tagline
+    stat_line = summary_stats_string("all")
+    day_stat_line = summary_stats_string("day")
+
+
+    # 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 = 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 = get_top_maps_by_games(10)
+
+    # 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]
 
     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,
+            }
+
+
+def main_index(request):
+    """
+    Display the main page information.
+    """
+    return _main_index_data(request)
+
+
+def main_index_json(request):
+    """
+    JSON output of the main page information.
+    """
+    return [{'status':'not implemented'}]
+
+
+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,
+            'query':query,
+            'start':start,
             }