source: binary-improvements2/WebServer/src/WebAPI/GetPlayerList.cs@ 394

Last change on this file since 394 was 394, checked in by alloc, 2 years ago

SessionHandler cleanup + redirect to /app/error/:code
Some profiler usage cleanup

File size: 8.2 KB
Line 
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Text.RegularExpressions;
5using AllocsFixes.JSON;
6using AllocsFixes.PersistentData;
7using JetBrains.Annotations;
8
9namespace Webserver.WebAPI {
10 [UsedImplicitly]
11 public class GetPlayerList : AbsWebAPI {
12 private static readonly Regex numberFilterMatcher =
13 new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$");
14
15 private static readonly UnityEngine.Profiling.CustomSampler jsonSerializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Build");
16
17 public override void HandleRequest (RequestContext _context) {
18 AdminTools admTools = GameManager.Instance.adminTools;
19 PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
20
21 bool bViewAll = WebConnection.CanViewAllPlayers (_context.PermissionLevel);
22
23 // TODO: Sort (and filter?) prior to converting to JSON ... hard as how to get the correct column's data? (i.e. column name matches JSON object field names, not source data)
24
25 int rowsPerPage = 25;
26 if (_context.Request.QueryString ["rowsperpage"] != null) {
27 int.TryParse (_context.Request.QueryString ["rowsperpage"], out rowsPerPage);
28 }
29
30 int page = 0;
31 if (_context.Request.QueryString ["page"] != null) {
32 int.TryParse (_context.Request.QueryString ["page"], out page);
33 }
34
35 int firstEntry = page * rowsPerPage;
36
37 Players playersList = PersistentContainer.Instance.Players;
38
39
40 List<JsonObject> playerList = new List<JsonObject> ();
41
42 jsonSerializeSampler.Begin ();
43
44 foreach (KeyValuePair<PlatformUserIdentifierAbs, Player> kvp in playersList.Dict) {
45 Player p = kvp.Value;
46
47 if (bViewAll || p.PlatformId.Equals (userId)) {
48 JsonObject pos = new JsonObject ();
49 pos.Add ("x", new JsonNumber (p.LastPosition.x));
50 pos.Add ("y", new JsonNumber (p.LastPosition.y));
51 pos.Add ("z", new JsonNumber (p.LastPosition.z));
52
53 JsonObject pJson = new JsonObject ();
54 pJson.Add ("steamid", new JsonString (kvp.Key.CombinedString));
55 pJson.Add ("entityid", new JsonNumber (p.EntityID));
56 pJson.Add ("ip", new JsonString (p.IP));
57 pJson.Add ("name", new JsonString (p.Name));
58 pJson.Add ("online", new JsonBoolean (p.IsOnline));
59 pJson.Add ("position", pos);
60
61 pJson.Add ("totalplaytime", new JsonNumber (p.TotalPlayTime));
62 pJson.Add ("lastonline",
63 new JsonString (p.LastOnline.ToUniversalTime ().ToString ("yyyy-MM-ddTHH:mm:ssZ")));
64 pJson.Add ("ping", new JsonNumber (p.IsOnline ? p.ClientInfo.ping : -1));
65
66 JsonBoolean banned = admTools != null ? new JsonBoolean (admTools.IsBanned (kvp.Key, out _, out _)) : new JsonBoolean (false);
67
68 pJson.Add ("banned", banned);
69
70 playerList.Add (pJson);
71 }
72 }
73
74 jsonSerializeSampler.End ();
75
76 IEnumerable<JsonObject> list = playerList;
77
78 foreach (string key in _context.Request.QueryString.AllKeys) {
79 if (!string.IsNullOrEmpty (key) && key.StartsWith ("filter[")) {
80 string filterCol = key.Substring (key.IndexOf ('[') + 1);
81 filterCol = filterCol.Substring (0, filterCol.Length - 1);
82 string filterVal = _context.Request.QueryString.Get (key).Trim ();
83
84 list = ExecuteFilter (list, filterCol, filterVal);
85 }
86 }
87
88 int totalAfterFilter = list.Count ();
89
90 foreach (string key in _context.Request.QueryString.AllKeys) {
91 if (!string.IsNullOrEmpty (key) && key.StartsWith ("sort[")) {
92 string sortCol = key.Substring (key.IndexOf ('[') + 1);
93 sortCol = sortCol.Substring (0, sortCol.Length - 1);
94 string sortVal = _context.Request.QueryString.Get (key);
95
96 list = ExecuteSort (list, sortCol, sortVal == "0");
97 }
98 }
99
100 list = list.Skip (firstEntry);
101 list = list.Take (rowsPerPage);
102
103
104 JsonArray playersJsResult = new JsonArray ();
105 foreach (JsonObject jsO in list) {
106 playersJsResult.Add (jsO);
107 }
108
109 JsonObject result = new JsonObject ();
110 result.Add ("total", new JsonNumber (totalAfterFilter));
111 result.Add ("totalUnfiltered", new JsonNumber (playerList.Count));
112 result.Add ("firstResult", new JsonNumber (firstEntry));
113 result.Add ("players", playersJsResult);
114
115 WebUtils.WriteJson (_context.Response, result);
116 }
117
118 private IEnumerable<JsonObject> ExecuteFilter (IEnumerable<JsonObject> _list, string _filterCol,
119 string _filterVal) {
120 if (!_list.Any()) {
121 return _list;
122 }
123
124 if (_list.First ().ContainsKey (_filterCol)) {
125 Type colType = _list.First () [_filterCol].GetType ();
126 if (colType == typeof (JsonNumber)) {
127 return ExecuteNumberFilter (_list, _filterCol, _filterVal);
128 }
129
130 if (colType == typeof (JsonBoolean)) {
131 bool value = StringParsers.ParseBool (_filterVal);
132 return _list.Where (_line => ((JsonBoolean) _line [_filterCol]).GetBool () == value);
133 }
134
135 if (colType == typeof (JsonString)) {
136 // regex-match whole ^string$, replace * by .*, ? by .?, + by .+
137 _filterVal = _filterVal.Replace ("*", ".*").Replace ("?", ".?").Replace ("+", ".+");
138 _filterVal = "^" + _filterVal + "$";
139
140 //Log.Out ("GetPlayerList: Filter on String with Regex '" + _filterVal + "'");
141 Regex matcher = new Regex (_filterVal, RegexOptions.IgnoreCase);
142 return _list.Where (_line => matcher.IsMatch (((JsonString) _line [_filterCol]).GetString ()));
143 }
144 }
145
146 return _list;
147 }
148
149
150 private IEnumerable<JsonObject> ExecuteNumberFilter (IEnumerable<JsonObject> _list, string _filterCol,
151 string _filterVal) {
152 // allow value (exact match), =, ==, >=, >, <=, <
153 Match filterMatch = numberFilterMatcher.Match (_filterVal);
154 if (filterMatch.Success) {
155 double value = StringParsers.ParseDouble (filterMatch.Groups [2].Value);
156 NumberMatchType matchType;
157 double epsilon = value / 100000;
158 switch (filterMatch.Groups [1].Value) {
159 case "":
160 case "=":
161 case "==":
162 matchType = NumberMatchType.Equal;
163 break;
164 case ">":
165 matchType = NumberMatchType.Greater;
166 break;
167 case ">=":
168 case "=>":
169 matchType = NumberMatchType.GreaterEqual;
170 break;
171 case "<":
172 matchType = NumberMatchType.Lesser;
173 break;
174 case "<=":
175 case "=<":
176 matchType = NumberMatchType.LesserEqual;
177 break;
178 default:
179 matchType = NumberMatchType.Equal;
180 break;
181 }
182
183 return _list.Where (delegate (JsonObject _line) {
184 double objVal = ((JsonNumber) _line [_filterCol]).GetDouble ();
185 switch (matchType) {
186 case NumberMatchType.Greater:
187 return objVal > value;
188 case NumberMatchType.GreaterEqual:
189 return objVal >= value;
190 case NumberMatchType.Lesser:
191 return objVal < value;
192 case NumberMatchType.LesserEqual:
193 return objVal <= value;
194 case NumberMatchType.Equal:
195 default:
196 return NearlyEqual (objVal, value, epsilon);
197 }
198 });
199 }
200
201 Log.Out ("GetPlayerList: ignoring invalid filter for number-column '{0}': '{1}'", _filterCol, _filterVal);
202 return _list;
203 }
204
205
206 private IEnumerable<JsonObject> ExecuteSort (IEnumerable<JsonObject> _list, string _sortCol, bool _ascending) {
207 if (_list.Count () == 0) {
208 return _list;
209 }
210
211 if (_list.First ().ContainsKey (_sortCol)) {
212 Type colType = _list.First () [_sortCol].GetType ();
213 if (colType == typeof (JsonNumber)) {
214 if (_ascending) {
215 return _list.OrderBy (_line => ((JsonNumber) _line [_sortCol]).GetDouble ());
216 }
217
218 return _list.OrderByDescending (_line => ((JsonNumber) _line [_sortCol]).GetDouble ());
219 }
220
221 if (colType == typeof (JsonBoolean)) {
222 if (_ascending) {
223 return _list.OrderBy (_line => ((JsonBoolean) _line [_sortCol]).GetBool ());
224 }
225
226 return _list.OrderByDescending (_line => ((JsonBoolean) _line [_sortCol]).GetBool ());
227 }
228
229 if (_ascending) {
230 return _list.OrderBy (_line => _line [_sortCol].ToString ());
231 }
232
233 return _list.OrderByDescending (_line => _line [_sortCol].ToString ());
234 }
235
236 return _list;
237 }
238
239
240 private bool NearlyEqual (double _a, double _b, double _epsilon) {
241 double absA = Math.Abs (_a);
242 double absB = Math.Abs (_b);
243 double diff = Math.Abs (_a - _b);
244
245 if (_a == _b) {
246 return true;
247 }
248
249 if (_a == 0 || _b == 0 || diff < double.Epsilon) {
250 return diff < _epsilon;
251 }
252
253 return diff / (absA + absB) < _epsilon;
254 }
255
256 private enum NumberMatchType {
257 Equal,
258 Greater,
259 GreaterEqual,
260 Lesser,
261 LesserEqual
262 }
263 }
264}
Note: See TracBrowser for help on using the repository browser.