]> de.git.xonotic.org Git - xonotic/xonstat.git/blob - xonstat/views/server.py
c0c01a9ea6ad8dc3e2e682b6ef6f12c0b61c49a1
[xonotic/xonstat.git] / xonstat / views / server.py
1 import logging
2 import sqlalchemy.sql.functions as func
3 import sqlalchemy.sql.expression as expr
4 from datetime import datetime, timedelta
5 from pyramid.httpexceptions import HTTPNotFound
6 from webhelpers.paginate import Page
7 from xonstat.models import DBSession, Player, Server, Map, Game, PlayerGameStat
8 from xonstat.util import page_url, html_colors
9 from xonstat.views.helpers import RecentGame, recent_games_q
10
11 log = logging.getLogger(__name__)
12
13
14 # Defaults
15 LEADERBOARD_LIFETIME = 30
16 LEADERBOARD_COUNT = 10
17 RECENT_GAMES_COUNT = 20
18
19
20 class ServerIndex(object):
21     """Returns a list of servers."""
22
23     def __init__(self, request):
24         """Common parameter parsing."""
25         self.request = request
26         self.page = request.params.get("page", 1)
27         self.servers = self.raw()
28
29     def raw(self):
30         """Returns the raw data shared by all renderers."""
31         try:
32             server_q = DBSession.query(Server).order_by(Server.server_id.desc())
33             servers = Page(server_q, self.page, items_per_page=25, url=page_url)
34
35         except:
36             servers = None
37
38         return servers
39
40     def html(self):
41         """For rendering this data using something HTML-based."""
42         return {
43             'servers': self.servers,
44         }
45
46     def json(self):
47         """For rendering this data using JSON."""
48         return {
49             'servers': [s.to_dict() for s in self.servers],
50         }
51
52
53 class ServerInfoBase(object):
54     """Baseline parameter parsing for Server URLs with a server_id in them."""
55
56     def __init__(self, request, limit=None, last=None):
57         """Common parameter parsing."""
58         self.request = request
59         self.server_id = request.matchdict.get("id", None)
60
61         raw_lifetime = request.registry.settings.get('xonstat.leaderboard_lifetime',
62                                                      LEADERBOARD_LIFETIME)
63         self.lifetime = int(raw_lifetime)
64
65         self.limit = request.matchdict.get("limit", limit)
66         self.last = request.matchdict.get("last", last)
67         self.now = datetime.utcnow()
68
69
70 class ServerTopMaps(ServerInfoBase):
71     """Returns the top maps played on a given server."""
72
73     def __init__(self, request, limit=None, last=None):
74         """Common parameter parsing."""
75         super(ServerTopMaps, self).__init__(request, limit, last)
76
77         self.top_maps = self.raw()
78
79     def raw(self):
80         """Returns the raw data shared by all renderers."""
81         try:
82             top_maps_q = DBSession.query(Game.map_id, Map.name, func.count())\
83                 .filter(Map.map_id==Game.map_id)\
84                 .filter(Game.server_id==self.server_id)\
85                 .filter(Game.create_dt > (self.now - timedelta(days=self.lifetime)))\
86                 .group_by(Game.map_id)\
87                 .group_by(Map.name) \
88                 .order_by(expr.desc(func.count()))
89
90             if self.last:
91                 top_maps_q = top_maps_q.offset(self.last)
92
93             if self.limit:
94                 top_maps_q = top_maps_q.limit(self.limit)
95
96             top_maps = top_maps_q.all()
97         except:
98             top_maps = None
99
100         return top_maps
101
102     def json(self):
103         """For rendering this data using JSON."""
104         top_maps = [{
105             "map_id": tm.map_id,
106             "map_name": tm.name,
107             "times_played": tm[2],
108         } for tm in self.top_maps]
109
110         return top_maps
111
112
113 class ServerTopScorers(ServerInfoBase):
114     """Returns the top scorers on a given server."""
115
116     def __init__(self, request, limit=None, last=None):
117         """Common parameter parsing."""
118         super(ServerTopScorers, self).__init__(request, limit, last)
119         self.top_scorers = self.raw()
120
121     def raw(self):
122         """Top scorers on this server by total score."""
123         try:
124             top_scorers_q = DBSession.query(Player.player_id, Player.nick,
125                                           func.sum(PlayerGameStat.score))\
126                 .filter(Player.player_id == PlayerGameStat.player_id)\
127                 .filter(Game.game_id == PlayerGameStat.game_id)\
128                 .filter(Game.server_id == self.server_id)\
129                 .filter(Player.player_id > 2)\
130                 .filter(PlayerGameStat.create_dt >
131                         (self.now - timedelta(days=LEADERBOARD_LIFETIME)))\
132                 .order_by(expr.desc(func.sum(PlayerGameStat.score)))\
133                 .group_by(Player.nick)\
134                 .group_by(Player.player_id)
135
136             if self.last:
137                 top_scorers_q = top_scorers_q.offset(self.last)
138
139             if self.limit:
140                 top_scorers_q = top_scorers_q.limit(self.limit)
141
142             top_scorers = top_scorers_q.all()
143
144         except:
145             top_scorers = None
146
147         return top_scorers
148
149     def json(self):
150         """For rendering this data using JSON."""
151         top_scorers = [{
152             "player_id": ts.player_id,
153             "nick": ts.nick,
154             "score": ts[2],
155         } for ts in self.top_scorers]
156
157         return top_scorers
158
159
160 class ServerTopPlayers(ServerInfoBase):
161     """Returns the top players by playing time on a given server."""
162
163     def __init__(self, request, limit=None, last=None):
164         """Common parameter parsing."""
165         super(ServerTopPlayers, self).__init__(request, limit, last)
166         self.top_players = self.raw()
167
168     def raw(self):
169         """Top players on this server by total playing time."""
170         try:
171             top_players_q = DBSession.query(Player.player_id, Player.nick,
172                                           func.sum(PlayerGameStat.alivetime))\
173                 .filter(Player.player_id == PlayerGameStat.player_id)\
174                 .filter(Game.game_id == PlayerGameStat.game_id)\
175                 .filter(Game.server_id == self.server_id)\
176                 .filter(Player.player_id > 2)\
177                 .filter(PlayerGameStat.create_dt > (self.now - timedelta(days=self.lifetime)))\
178                 .order_by(expr.desc(func.sum(PlayerGameStat.alivetime)))\
179                 .group_by(Player.nick)\
180                 .group_by(Player.player_id)
181
182             if self.last:
183                 top_players_q = top_players_q.offset(self.last)
184
185             if self.limit:
186                 top_players_q = top_players_q.limit(self.limit)
187
188             top_players = top_players_q.all()
189
190         except:
191             top_players = None
192
193         return top_players
194
195     def json(self):
196         """For rendering this data using JSON."""
197         top_players = [{
198             "player_id": ts.player_id,
199             "nick": ts.nick,
200             "time": ts[2].total_seconds(),
201         } for ts in self.top_players]
202
203         return top_players
204
205
206 class ServerInfo(ServerInfoBase):
207     """Returns detailed information about a particular server."""
208
209     def __init__(self, request):
210         """Common data and parameters."""
211         super(ServerInfo, self).__init__(request)
212
213         # this view uses data from other views, so we'll save the data at that level
214         try:
215             self.server = DBSession.query(Server).filter_by(server_id=self.server_id).one()
216             self.top_maps_v = ServerTopMaps(self.request, limit=LEADERBOARD_COUNT)
217             self.top_scorers_v = ServerTopScorers(self.request, limit=LEADERBOARD_COUNT)
218             self.top_players_v = ServerTopPlayers(self.request, limit=LEADERBOARD_COUNT)
219
220             rgs = recent_games_q(server_id=self.server_id).limit(RECENT_GAMES_COUNT).all()
221             self.recent_games = [RecentGame(row) for row in rgs]
222         except:
223             raise HTTPNotFound
224
225     def raw(self):
226         """Returns the raw data shared by all renderers."""
227         return {
228             'server': self.server,
229             'top_players': self.top_players_v.top_players,
230             'top_scorers': self.top_scorers_v.top_scorers,
231             'top_maps': self.top_maps_v.top_maps,
232             'recent_games': self.recent_games,
233         }
234
235     def html(self):
236         """For rendering this data using something HTML-based."""
237         server_info = self.raw()
238
239         # convert the nick into HTML for both scorers and players
240         server_info["top_scorers"] = [(player_id, html_colors(nick), score)
241                                       for (player_id, nick, score) in server_info["top_scorers"]]
242
243         server_info["top_players"] = [(player_id, html_colors(nick), score)
244                                       for (player_id, nick, score) in server_info["top_players"]]
245
246         return server_info
247
248     def json(self):
249         """For rendering this data using JSON."""
250
251         return {
252             'server': self.server.to_dict(),
253             'top_players': self.top_players_v.json(),
254             'top_scorers': self.top_scorers_v.json(),
255             'top_maps': self.top_maps_v.json(),
256             'recent_games': [rg.to_dict() for rg in self.recent_games],
257         }