Change around the organization of ServerInfo.
[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):
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.now = datetime.utcnow()
66
67
68 class ServerTopMaps(ServerInfoBase):
69     """Returns the top maps played on a given server."""
70
71     def __init__(self, request):
72         """Common parameter parsing."""
73         super(ServerTopMaps, self).__init__(request)
74         self.top_maps = self.raw()
75
76     def raw(self):
77         """Returns the raw data shared by all renderers."""
78         try:
79             top_maps = DBSession.query(Game.map_id, Map.name, func.count())\
80                 .filter(Map.map_id==Game.map_id)\
81                 .filter(Game.server_id==self.server_id)\
82                 .filter(Game.create_dt > (self.now - timedelta(days=self.lifetime)))\
83                 .group_by(Game.map_id)\
84                 .group_by(Map.name) \
85                 .order_by(expr.desc(func.count()))\
86                 .limit(LEADERBOARD_COUNT)\
87                 .all()
88         except:
89             top_maps = None
90
91         return top_maps
92
93     def json(self):
94         """For rendering this data using JSON."""
95         top_maps = [{
96             "map_id": tm.map_id,
97             "map_name": tm.name,
98             "times_played": tm[2],
99         } for tm in self.top_maps]
100
101         return top_maps
102
103
104 class ServerTopScorers(ServerInfoBase):
105     """Returns the top scorers on a given server."""
106
107     def __init__(self, request):
108         """Common parameter parsing."""
109         super(ServerTopScorers, self).__init__(request)
110         self.top_scorers = self.raw()
111
112     def raw(self):
113         """Top scorers on this server by total score."""
114         try:
115             top_scorers = DBSession.query(Player.player_id, Player.nick,
116                                           func.sum(PlayerGameStat.score))\
117                 .filter(Player.player_id == PlayerGameStat.player_id)\
118                 .filter(Game.game_id == PlayerGameStat.game_id)\
119                 .filter(Game.server_id == self.server_id)\
120                 .filter(Player.player_id > 2)\
121                 .filter(PlayerGameStat.create_dt >
122                         (self.now - timedelta(days=LEADERBOARD_LIFETIME)))\
123                 .order_by(expr.desc(func.sum(PlayerGameStat.score)))\
124                 .group_by(Player.nick)\
125                 .group_by(Player.player_id)\
126                 .limit(LEADERBOARD_COUNT)\
127                 .all()
128
129         except:
130             top_scorers = None
131
132         return top_scorers
133
134     def json(self):
135         """For rendering this data using JSON."""
136         top_scorers = [{
137             "player_id": ts.player_id,
138             "nick": ts.nick,
139             "score": ts[2],
140         } for ts in self.top_scorers]
141
142         return top_scorers
143
144
145 class ServerTopPlayers(ServerInfoBase):
146     """Returns the top players by playing time on a given server."""
147
148     def __init__(self, request):
149         """Common parameter parsing."""
150         super(ServerTopPlayers, self).__init__(request)
151         self.top_players = self.raw()
152
153     def raw(self):
154         """Top players on this server by total playing time."""
155         try:
156             top_players = DBSession.query(Player.player_id, Player.nick,
157                                           func.sum(PlayerGameStat.alivetime))\
158                 .filter(Player.player_id == PlayerGameStat.player_id)\
159                 .filter(Game.game_id == PlayerGameStat.game_id)\
160                 .filter(Game.server_id == self.server_id)\
161                 .filter(Player.player_id > 2)\
162                 .filter(PlayerGameStat.create_dt > (self.now - timedelta(days=self.lifetime)))\
163                 .order_by(expr.desc(func.sum(PlayerGameStat.alivetime)))\
164                 .group_by(Player.nick)\
165                 .group_by(Player.player_id)\
166                 .limit(LEADERBOARD_COUNT)\
167                 .all()
168
169         except:
170             top_players = None
171
172         return top_players
173
174     def json(self):
175         """For rendering this data using JSON."""
176         top_players = [{
177             "player_id": ts.player_id,
178             "nick": ts.nick,
179             "time": ts[2].total_seconds(),
180         } for ts in self.top_players]
181
182         return top_players
183
184
185 class ServerInfo(ServerInfoBase):
186     """Returns detailed information about a particular server."""
187
188     def __init__(self, request):
189         """Common data and parameters."""
190         super(ServerInfo, self).__init__(request)
191
192         # this view uses data from other views, so we'll save the data at that level
193         try:
194             self.server = DBSession.query(Server).filter_by(server_id=self.server_id).one()
195             self.top_maps_v = ServerTopMaps(self.request)
196             self.top_scorers_v = ServerTopScorers(self.request)
197             self.top_players_v = ServerTopPlayers(self.request)
198
199             rgs = recent_games_q(server_id=self.server_id).limit(RECENT_GAMES_COUNT).all()
200             self.recent_games = [RecentGame(row) for row in rgs]
201         except:
202             raise HTTPNotFound
203
204     def raw(self):
205         """Returns the raw data shared by all renderers."""
206         return {
207             'server': self.server,
208             'top_players': self.top_players_v.top_players,
209             'top_scorers': self.top_scorers_v.top_scorers,
210             'top_maps': self.top_maps_v.top_maps,
211             'recent_games': self.recent_games,
212         }
213
214     def html(self):
215         """For rendering this data using something HTML-based."""
216         server_info = self.raw()
217
218         print(server_info)
219
220         # convert the nick into HTML for both scorers and players
221         server_info["top_scorers"] = [(player_id, html_colors(nick), score)
222                                       for (player_id, nick, score) in server_info["top_scorers"]]
223
224         server_info["top_players"] = [(player_id, html_colors(nick), score)
225                                       for (player_id, nick, score) in server_info["top_players"]]
226
227         return server_info
228
229     def json(self):
230         """For rendering this data using JSON."""
231
232         return {
233             'server': self.server.to_dict(),
234             'top_players': self.top_players_v.json(),
235             'top_scorers': self.top_scorers_v.json(),
236             'top_maps': self.top_maps_v.json(),
237             'recent_games': [rg.to_dict() for rg in self.recent_games],
238         }