source: binary-improvements/MapRendering/API/GetPlayerList.cs@ 477

Last change on this file since 477 was 455, checked in by alloc, 16 months ago

25_30_44

  • Got rid (mostly) of custom JSON serialization
  • Some code cleanup
File size: 13.4 KB
RevLine 
[279]1using System;
2using System.Collections.Generic;
3using System.Linq;
[455]4using System.Text;
[279]5using System.Text.RegularExpressions;
[325]6using AllocsFixes.PersistentData;
[455]7using JetBrains.Annotations;
[454]8using Webserver;
9using Webserver.Permissions;
10using Webserver.WebAPI;
[279]11
[454]12namespace AllocsFixes.WebAPIs {
[455]13 [UsedImplicitly]
[454]14 public class GetPlayerList : AbsWebAPI {
[325]15 private static readonly Regex numberFilterMatcher =
16 new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$");
[279]17
[332]18#if ENABLE_PROFILER
[369]19 private static readonly UnityEngine.Profiling.CustomSampler jsonSerializeSampler = UnityEngine.Profiling.CustomSampler.Create ("JSON_Build");
[332]20#endif
21
[454]22 public override void HandleRequest (RequestContext _context) {
[325]23 AdminTools admTools = GameManager.Instance.adminTools;
[454]24 PlatformUserIdentifierAbs userId = _context.Connection?.UserId;
[279]25
[454]26 bool bViewAll = PermissionUtils.CanViewAllPlayers (_context.PermissionLevel);
[279]27
28 // 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)
29
30 int rowsPerPage = 25;
[454]31 if (_context.Request.QueryString ["rowsperpage"] != null) {
32 int.TryParse (_context.Request.QueryString ["rowsperpage"], out rowsPerPage);
[279]33 }
34
35 int page = 0;
[454]36 if (_context.Request.QueryString ["page"] != null) {
37 int.TryParse (_context.Request.QueryString ["page"], out page);
[279]38 }
39
40 int firstEntry = page * rowsPerPage;
41
42 Players playersList = PersistentContainer.Instance.Players;
43
[332]44
[279]45 List<JSONObject> playerList = new List<JSONObject> ();
46
[332]47#if ENABLE_PROFILER
48 jsonSerializeSampler.Begin ();
49#endif
[279]50
[369]51 foreach (KeyValuePair<PlatformUserIdentifierAbs, Player> kvp in playersList.Dict) {
[332]52 Player p = kvp.Value;
53
[446]54 if (bViewAll || p.InternalId.Equals (userId)) {
[325]55 JSONObject pos = new JSONObject ();
56 pos.Add ("x", new JSONNumber (p.LastPosition.x));
57 pos.Add ("y", new JSONNumber (p.LastPosition.y));
58 pos.Add ("z", new JSONNumber (p.LastPosition.z));
[279]59
[325]60 JSONObject pJson = new JSONObject ();
[446]61 pJson.Add ("steamid", new JSONString (kvp.Value.PlatformId.CombinedString));
62 pJson.Add ("crossplatformid", new JSONString (kvp.Value.CrossPlatformId?.CombinedString ?? ""));
[325]63 pJson.Add ("entityid", new JSONNumber (p.EntityID));
64 pJson.Add ("ip", new JSONString (p.IP));
65 pJson.Add ("name", new JSONString (p.Name));
66 pJson.Add ("online", new JSONBoolean (p.IsOnline));
67 pJson.Add ("position", pos);
[279]68
69 pJson.Add ("totalplaytime", new JSONNumber (p.TotalPlayTime));
[325]70 pJson.Add ("lastonline",
71 new JSONString (p.LastOnline.ToUniversalTime ().ToString ("yyyy-MM-ddTHH:mm:ssZ")));
[279]72 pJson.Add ("ping", new JSONNumber (p.IsOnline ? p.ClientInfo.ping : -1));
73
[420]74 JSONBoolean banned = admTools != null ? new JSONBoolean (admTools.Blacklist.IsBanned (kvp.Key, out _, out _)) : new JSONBoolean (false);
[325]75
[279]76 pJson.Add ("banned", banned);
77
78 playerList.Add (pJson);
[325]79 }
80 }
[279]81
[332]82#if ENABLE_PROFILER
83 jsonSerializeSampler.End ();
84#endif
85
[279]86 IEnumerable<JSONObject> list = playerList;
87
[454]88 foreach (string key in _context.Request.QueryString.AllKeys) {
[279]89 if (!string.IsNullOrEmpty (key) && key.StartsWith ("filter[")) {
90 string filterCol = key.Substring (key.IndexOf ('[') + 1);
91 filterCol = filterCol.Substring (0, filterCol.Length - 1);
[454]92 string filterVal = _context.Request.QueryString.Get (key).Trim ();
[279]93
94 list = ExecuteFilter (list, filterCol, filterVal);
95 }
96 }
97
98 int totalAfterFilter = list.Count ();
99
[454]100 foreach (string key in _context.Request.QueryString.AllKeys) {
[279]101 if (!string.IsNullOrEmpty (key) && key.StartsWith ("sort[")) {
102 string sortCol = key.Substring (key.IndexOf ('[') + 1);
103 sortCol = sortCol.Substring (0, sortCol.Length - 1);
[454]104 string sortVal = _context.Request.QueryString.Get (key);
[279]105
106 list = ExecuteSort (list, sortCol, sortVal == "0");
107 }
108 }
109
110 list = list.Skip (firstEntry);
111 list = list.Take (rowsPerPage);
112
113
114 JSONArray playersJsResult = new JSONArray ();
115 foreach (JSONObject jsO in list) {
116 playersJsResult.Add (jsO);
117 }
118
119 JSONObject result = new JSONObject ();
120 result.Add ("total", new JSONNumber (totalAfterFilter));
121 result.Add ("totalUnfiltered", new JSONNumber (playerList.Count));
122 result.Add ("firstResult", new JSONNumber (firstEntry));
123 result.Add ("players", playersJsResult);
124
[455]125 StringBuilder sb = new StringBuilder ();
126 result.ToString (sb);
127 WebUtils.WriteText (_context.Response, sb.ToString(), _mimeType: WebUtils.MimeJson);
[279]128 }
129
[455]130 private static IEnumerable<JSONObject> ExecuteFilter (IEnumerable<JSONObject> _list, string _filterCol,
[325]131 string _filterVal) {
[351]132 if (!_list.Any()) {
[279]133 return _list;
134 }
135
136 if (_list.First ().ContainsKey (_filterCol)) {
137 Type colType = _list.First () [_filterCol].GetType ();
[325]138 if (colType == typeof (JSONNumber)) {
[279]139 return ExecuteNumberFilter (_list, _filterCol, _filterVal);
[325]140 }
141
142 if (colType == typeof (JSONBoolean)) {
[326]143 bool value = StringParsers.ParseBool (_filterVal);
[351]144 return _list.Where (_line => ((JSONBoolean) _line [_filterCol]).GetBool () == value);
[325]145 }
146
147 if (colType == typeof (JSONString)) {
[279]148 // regex-match whole ^string$, replace * by .*, ? by .?, + by .+
149 _filterVal = _filterVal.Replace ("*", ".*").Replace ("?", ".?").Replace ("+", ".+");
[455]150 _filterVal = $"^{_filterVal}$";
[325]151
[279]152 //Log.Out ("GetPlayerList: Filter on String with Regex '" + _filterVal + "'");
153 Regex matcher = new Regex (_filterVal, RegexOptions.IgnoreCase);
[351]154 return _list.Where (_line => matcher.IsMatch (((JSONString) _line [_filterCol]).GetString ()));
[279]155 }
156 }
[325]157
[279]158 return _list;
159 }
160
161
[455]162 private static IEnumerable<JSONObject> ExecuteNumberFilter (IEnumerable<JSONObject> _list, string _filterCol,
[325]163 string _filterVal) {
[279]164 // allow value (exact match), =, ==, >=, >, <=, <
165 Match filterMatch = numberFilterMatcher.Match (_filterVal);
166 if (filterMatch.Success) {
[324]167 double value = StringParsers.ParseDouble (filterMatch.Groups [2].Value);
[279]168 NumberMatchType matchType;
169 double epsilon = value / 100000;
170 switch (filterMatch.Groups [1].Value) {
[325]171 case "":
172 case "=":
173 case "==":
174 matchType = NumberMatchType.Equal;
175 break;
176 case ">":
177 matchType = NumberMatchType.Greater;
178 break;
179 case ">=":
180 case "=>":
181 matchType = NumberMatchType.GreaterEqual;
182 break;
183 case "<":
184 matchType = NumberMatchType.Lesser;
185 break;
186 case "<=":
187 case "=<":
188 matchType = NumberMatchType.LesserEqual;
189 break;
190 default:
191 matchType = NumberMatchType.Equal;
192 break;
[279]193 }
[325]194
[351]195 return _list.Where (delegate (JSONObject _line) {
196 double objVal = ((JSONNumber) _line [_filterCol]).GetDouble ();
[279]197 switch (matchType) {
[325]198 case NumberMatchType.Greater:
199 return objVal > value;
200 case NumberMatchType.GreaterEqual:
201 return objVal >= value;
202 case NumberMatchType.Lesser:
203 return objVal < value;
204 case NumberMatchType.LesserEqual:
205 return objVal <= value;
206 case NumberMatchType.Equal:
207 default:
208 return NearlyEqual (objVal, value, epsilon);
[279]209 }
210 });
211 }
[325]212
[455]213 Log.Out ($"GetPlayerList: ignoring invalid filter for number-column '{_filterCol}': '{_filterVal}'");
[279]214 return _list;
215 }
216
217
[455]218 private static IEnumerable<JSONObject> ExecuteSort (IEnumerable<JSONObject> _list, string _sortCol, bool _ascending) {
[279]219 if (_list.Count () == 0) {
220 return _list;
221 }
222
223 if (_list.First ().ContainsKey (_sortCol)) {
224 Type colType = _list.First () [_sortCol].GetType ();
[325]225 if (colType == typeof (JSONNumber)) {
[279]226 if (_ascending) {
[351]227 return _list.OrderBy (_line => ((JSONNumber) _line [_sortCol]).GetDouble ());
[279]228 }
[325]229
[351]230 return _list.OrderByDescending (_line => ((JSONNumber) _line [_sortCol]).GetDouble ());
[325]231 }
232
233 if (colType == typeof (JSONBoolean)) {
[279]234 if (_ascending) {
[351]235 return _list.OrderBy (_line => ((JSONBoolean) _line [_sortCol]).GetBool ());
[279]236 }
[325]237
[351]238 return _list.OrderByDescending (_line => ((JSONBoolean) _line [_sortCol]).GetBool ());
[279]239 }
[325]240
241 if (_ascending) {
[351]242 return _list.OrderBy (_line => _line [_sortCol].ToString ());
[325]243 }
244
[351]245 return _list.OrderByDescending (_line => _line [_sortCol].ToString ());
[279]246 }
[325]247
[279]248 return _list;
249 }
250
251
[455]252 private static bool NearlyEqual (double _a, double _b, double _epsilon) {
[351]253 double absA = Math.Abs (_a);
254 double absB = Math.Abs (_b);
255 double diff = Math.Abs (_a - _b);
[279]256
[351]257 if (_a == _b) {
[279]258 return true;
[325]259 }
260
[351]261 if (_a == 0 || _b == 0 || diff < double.Epsilon) {
262 return diff < _epsilon;
[279]263 }
[325]264
[351]265 return diff / (absA + absB) < _epsilon;
[279]266 }
267
[325]268 private enum NumberMatchType {
269 Equal,
270 Greater,
271 GreaterEqual,
272 Lesser,
[455]273 LesserEqual,
[325]274 }
[455]275
276
277#region JSON encoder
278
279 private abstract class JSONNode {
280 public abstract void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0);
281
282 public override string ToString () {
283 StringBuilder sb = new StringBuilder ();
284 ToString (sb);
285 return sb.ToString ();
286 }
287 }
288
289 private abstract class JSONValue : JSONNode {
290 }
291
292 private class JSONNull : JSONValue {
293 public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
294 _stringBuilder.Append ("null");
295 }
296 }
297
298 private class JSONBoolean : JSONValue {
299 private readonly bool value;
300
301 public JSONBoolean (bool _value) {
302 value = _value;
303 }
304
305 public bool GetBool () {
306 return value;
307 }
308
309 public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
310 _stringBuilder.Append (value ? "true" : "false");
311 }
312 }
313
314 private class JSONNumber : JSONValue {
315 private readonly double value;
316
317 public JSONNumber (double _value) {
318 value = _value;
319 }
320
321 public double GetDouble () {
322 return value;
323 }
324
325 public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
326 _stringBuilder.Append (value.ToCultureInvariantString ());
327 }
328 }
329
330 private class JSONString : JSONValue {
331 private readonly string value;
332
333 public JSONString (string _value) {
334 value = _value;
335 }
336
337 public string GetString () {
338 return value;
339 }
340
341 public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
342 if (string.IsNullOrEmpty (value)) {
343 _stringBuilder.Append ("\"\"");
344 return;
345 }
346
347 int len = value.Length;
348
349 _stringBuilder.EnsureCapacity (_stringBuilder.Length + 2 * len);
350
351 _stringBuilder.Append ('"');
352
353 foreach (char c in value) {
354 switch (c) {
355 case '\\':
356 case '"':
357
358// case '/':
359 _stringBuilder.Append ('\\');
360 _stringBuilder.Append (c);
361 break;
362 case '\b':
363 _stringBuilder.Append ("\\b");
364 break;
365 case '\t':
366 _stringBuilder.Append ("\\t");
367 break;
368 case '\n':
369 _stringBuilder.Append ("\\n");
370 break;
371 case '\f':
372 _stringBuilder.Append ("\\f");
373 break;
374 case '\r':
375 _stringBuilder.Append ("\\r");
376 break;
377 default:
378 if (c < ' ') {
379 _stringBuilder.Append ("\\u");
380 _stringBuilder.Append (((int) c).ToString ("X4"));
381 } else {
382 _stringBuilder.Append (c);
383 }
384
385 break;
386 }
387 }
388
389 _stringBuilder.Append ('"');
390 }
391
392 }
393
394 private class JSONArray : JSONNode {
395 private readonly List<JSONNode> nodes = new List<JSONNode> ();
396
397 public void Add (JSONNode _node) {
398 nodes.Add (_node);
399 }
400
401 public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
402 _stringBuilder.Append ("[");
403 if (_prettyPrint) {
404 _stringBuilder.Append ('\n');
405 }
406
407 foreach (JSONNode n in nodes) {
408 if (_prettyPrint) {
409 _stringBuilder.Append (new string ('\t', _currentLevel + 1));
410 }
411
412 n.ToString (_stringBuilder, _prettyPrint, _currentLevel + 1);
413 _stringBuilder.Append (",");
414 if (_prettyPrint) {
415 _stringBuilder.Append ('\n');
416 }
417 }
418
419 if (nodes.Count > 0) {
420 _stringBuilder.Remove (_stringBuilder.Length - (_prettyPrint ? 2 : 1), 1);
421 }
422
423 if (_prettyPrint) {
424 _stringBuilder.Append (new string ('\t', _currentLevel));
425 }
426
427 _stringBuilder.Append ("]");
428 }
429
430 }
431
432 private class JSONObject : JSONNode {
433 private readonly Dictionary<string, JSONNode> nodes = new Dictionary<string, JSONNode> ();
434
435 public JSONNode this [string _name] => nodes [_name];
436
437 public bool ContainsKey (string _name) {
438 return nodes.ContainsKey (_name);
439 }
440
441 public void Add (string _name, JSONNode _node) {
442 nodes.Add (_name, _node);
443 }
444
445 public override void ToString (StringBuilder _stringBuilder, bool _prettyPrint = false, int _currentLevel = 0) {
446 _stringBuilder.Append ("{");
447 if (_prettyPrint) {
448 _stringBuilder.Append ('\n');
449 }
450
451 foreach (KeyValuePair<string, JSONNode> kvp in nodes) {
452 if (_prettyPrint) {
453 _stringBuilder.Append (new string ('\t', _currentLevel + 1));
454 }
455
456 _stringBuilder.Append ($"\"{kvp.Key}\":");
457 if (_prettyPrint) {
458 _stringBuilder.Append (" ");
459 }
460
461 kvp.Value.ToString (_stringBuilder, _prettyPrint, _currentLevel + 1);
462 _stringBuilder.Append (",");
463 if (_prettyPrint) {
464 _stringBuilder.Append ('\n');
465 }
466 }
467
468 if (nodes.Count > 0) {
469 _stringBuilder.Remove (_stringBuilder.Length - (_prettyPrint ? 2 : 1), 1);
470 }
471
472 if (_prettyPrint) {
473 _stringBuilder.Append (new string ('\t', _currentLevel));
474 }
475
476 _stringBuilder.Append ("}");
477 }
478
479 }
480
481#endregion
482
[279]483 }
[325]484}
Note: See TracBrowser for help on using the repository browser.