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