]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views/main.py
Make the active players/servers/maps use the cache.
[xonotic/xonstat.git] / xonstat / views / main.py
1 import logging
2 import sqlalchemy as sa
3 import sqlalchemy.sql.functions as func
4 import sqlalchemy.sql.expression as expr
5 from beaker.cache import cache_regions, cache_region
6 from collections import namedtuple
7 from datetime import datetime, timedelta
8 from pyramid.response import Response
9 from xonstat.models import *
10 from xonstat.util import *
11 from xonstat.views.helpers import RecentGame, recent_games_q
12 from webhelpers.paginate import Page
13
14
15 log = logging.getLogger(__name__)
16
17
18 @cache_region('hourly_term')
19 def get_summary_stats(scope="all"):
20     """
21     Gets the following aggregate statistics according to the provided scope:
22
23         - the number of active players
24         - the number of games per game type
25
26     Scope can be "all" or "day".
27
28     The fetched information is summarized into a string which is passed
29     directly to the template.
30     """
31     if scope not in ["all", "day"]:
32         scope = "all"
33
34     try:
35         ss = DBSession.query("num_players", "game_type_cd", "num_games").\
36                 from_statement(
37                         "SELECT num_players, game_type_cd, num_games "
38                         "FROM summary_stats_mv "
39                         "WHERE scope = :scope "
40                         "ORDER BY sort_order "
41                 ).params(scope=scope).all()
42
43         i = 1
44         total_games = 0
45         other_games = 0
46         for row in ss:
47             # the number of players is constant in each row
48             total_players = row.num_players
49
50             total_games += row.num_games
51
52             # we can't show all game types on the single summary line, so any
53             # past the fifth one will get bundled in to an "other" count
54             if i > 5:
55                 other_games += row.num_games
56
57             i += 1
58
59         # don't send anything if we don't have any activity
60         if total_games == 0:
61             stat_line = None
62         else:
63         # This is ugly because we're doing template-like stuff within the
64         # view code. The alternative isn't any better, though: we would
65         # have to assemble the string inside the template by using a Python
66         # code block. For now I'll leave it like this since it is the lesser
67         # of two evils IMO.
68         # Also we need to hard-code the URL structure in here to allow caching,
69         # which also sucks.
70             in_paren = "; ".join(["{:2,d} {}".format(
71                 g.num_games,
72                 "<a href='/games?type={0}'>{0}</a>".format(g.game_type_cd)
73             ) for g in ss[:5]])
74
75             if other_games > 0:
76                 in_paren += "; {:2,d} other".format(other_games)
77
78             stat_line = "{:2,d} players and {:2,d} games ({})".format(
79                 total_players,
80                 total_games,
81                 in_paren
82             )
83
84     except Exception as e:
85         raise e
86         stat_line = None
87
88     return stat_line
89
90
91 @cache_region('hourly_term')
92 def get_ranks(game_type_cd):
93     """
94     Gets a set number of the top-ranked people for the specified game_type_cd.
95
96     The game_type_cd parameter is the type to fetch. Currently limited to
97     duel, dm, ctf, and tdm.
98     """
99     # how many ranks we want to fetch
100     leaderboard_count = 10
101
102     # only a few game modes are actually ranked
103     if game_type_cd not in 'duel' 'dm' 'ctf' 'tdm':
104         return None
105
106     ranks = DBSession.query(PlayerRank).\
107             filter(PlayerRank.game_type_cd==game_type_cd).\
108             order_by(PlayerRank.rank).\
109             limit(leaderboard_count).all()
110
111     return ranks
112
113
114 @cache_region('hourly_term')
115 def get_top_players_by_time(limit=None, start=None):
116     """
117     The top players by the amount of time played during a date range.
118     """
119     q = DBSession.query(ActivePlayer)
120
121     if start is not None:
122         q = q.filter(ActivePlayer.sort_order >= start)
123
124     q = q.order_by(ActivePlayer.sort_order)
125
126     if limit is not None:
127         q = q.limit(limit)
128
129     return q.all()
130
131
132 @cache_region('hourly_term')
133 def get_top_servers_by_games(limit=None, start=None):
134     """
135     The top servers by the number of games played during a date range.
136     """
137     q = DBSession.query(ActiveServer)
138
139     if start is not None:
140         q = q.filter(ActiveServer.sort_order >= start)
141
142     q = q.order_by(ActiveServer.sort_order)
143
144     if limit is not None:
145         q = q.limit(limit)
146
147     return q.all()
148
149
150 @cache_region('hourly_term')
151 def get_top_maps_by_games(limit=None, start=None):
152     """
153     The top maps by the number of games played during a date range.
154     """
155     q = DBSession.query(ActiveMap)
156
157     if start is not None:
158         q = q.filter(ActiveMap.sort_order >= start)
159
160     q = q.order_by(ActiveMap.sort_order)
161
162     if limit is not None:
163         q = q.limit(limit)
164
165     return q.all()
166
167
168 def _main_index_data(request):
169     try:
170         leaderboard_lifetime = int(
171                 request.registry.settings['xonstat.leaderboard_lifetime'])
172     except:
173         leaderboard_lifetime = 30
174
175     leaderboard_count = 10
176     recent_games_count = 20
177
178     # summary statistics for the tagline
179     stat_line = get_summary_stats("all")
180     day_stat_line = get_summary_stats("day")
181
182
183     # the three top ranks tables
184     ranks = []
185     for gtc in ['duel', 'ctf', 'dm', 'tdm']:
186         rank = get_ranks(gtc)
187         if len(rank) != 0:
188             ranks.append(rank)
189
190     right_now = datetime.utcnow()
191     back_then = datetime.utcnow() - timedelta(days=leaderboard_lifetime)
192
193     # top players by playing time
194     top_players = get_top_players_by_time(10)
195
196     # top servers by number of games
197     top_servers = get_top_servers_by_games(10)
198
199     # top maps by total times played
200     top_maps = get_top_maps_by_games(10)
201
202     # recent games played in descending order
203     rgs = recent_games_q(cutoff=back_then).limit(recent_games_count).all()
204     recent_games = [RecentGame(row) for row in rgs]
205
206     return {'top_players':top_players,
207             'top_servers':top_servers,
208             'top_maps':top_maps,
209             'recent_games':recent_games,
210             'ranks':ranks,
211             'stat_line':stat_line,
212             'day_stat_line':day_stat_line,
213             }
214
215
216 def main_index(request):
217     """
218     Display the main page information.
219     """
220     return _main_index_data(request)
221
222
223 def main_index_json(request):
224     """
225     JSON output of the main page information.
226     """
227     return [{'status':'not implemented'}]
228
229
230 def top_players_index(request):
231     try:
232         start = int(request.params.get('start', None))
233     except:
234         start = None
235
236     top_players = get_top_players_by_time(20, start)
237
238     # building a query string
239     query = {}
240     if len(top_players) > 1:
241         query['start'] = top_players[-1].sort_order + 1
242
243     return {
244             'top_players':top_players,
245             'query':query,
246             'start':start,
247             }
248
249
250 def top_servers_index(request):
251     try:
252         start = int(request.params.get('start', None))
253     except:
254         start = None
255
256     top_servers = get_top_servers_by_games(20, start)
257
258     # building a query string
259     query = {}
260     if len(top_servers) > 1:
261         query['start'] = top_servers[-1].sort_order + 1
262
263     return {
264             'top_servers':top_servers,
265             'query':query,
266             'start':start,
267             }
268
269
270 def top_maps_index(request):
271     try:
272         start = int(request.params.get('start', None))
273     except:
274         start = None
275
276     top_maps = get_top_maps_by_games(20, start)
277
278     # building a query string
279     query = {}
280     if len(top_maps) > 1:
281         query['start'] = top_maps[-1].sort_order + 1
282
283     return {
284             'top_maps':top_maps,
285             'query':query,
286             'start':start,
287             }