| [279] | 1 | using AllocsFixes.JSON;
 | 
|---|
 | 2 | using AllocsFixes.PersistentData;
 | 
|---|
 | 3 | using System;
 | 
|---|
 | 4 | using System.Collections.Generic;
 | 
|---|
 | 5 | using System.Linq;
 | 
|---|
 | 6 | using System.Net;
 | 
|---|
 | 7 | using System.Text.RegularExpressions;
 | 
|---|
 | 8 | 
 | 
|---|
 | 9 | namespace AllocsFixes.NetConnections.Servers.Web.API
 | 
|---|
 | 10 | {
 | 
|---|
 | 11 |         public class GetPlayerList : WebAPI
 | 
|---|
 | 12 |         {
 | 
|---|
 | 13 |                 private static Regex numberFilterMatcher = new Regex (@"^(>=|=>|>|<=|=<|<|==|=)?\s*([0-9]+(\.[0-9]*)?)$");
 | 
|---|
 | 14 |                 private enum NumberMatchType {
 | 
|---|
 | 15 |                         Equal,
 | 
|---|
 | 16 |                         Greater,
 | 
|---|
 | 17 |                         GreaterEqual,
 | 
|---|
 | 18 |                         Lesser,
 | 
|---|
 | 19 |                         LesserEqual
 | 
|---|
 | 20 |                 }
 | 
|---|
 | 21 | 
 | 
|---|
 | 22 |                 public override void HandleRequest (HttpListenerRequest req, HttpListenerResponse resp, WebConnection user, int permissionLevel)
 | 
|---|
 | 23 |                 {
 | 
|---|
 | 24 |             AdminTools admTools = GameManager.Instance.adminTools;
 | 
|---|
 | 25 |             user = user ?? new WebConnection ("", "", 0L);
 | 
|---|
 | 26 | 
 | 
|---|
 | 27 |             bool bViewAll = WebConnection.CanViewAllPlayers (permissionLevel);
 | 
|---|
 | 28 | 
 | 
|---|
 | 29 |                         // 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)
 | 
|---|
 | 30 | 
 | 
|---|
 | 31 |                         int rowsPerPage = 25;
 | 
|---|
 | 32 |                         if (req.QueryString ["rowsperpage"] != null) {
 | 
|---|
 | 33 |                                 int.TryParse (req.QueryString ["rowsperpage"], out rowsPerPage);
 | 
|---|
 | 34 |                         }
 | 
|---|
 | 35 | 
 | 
|---|
 | 36 |                         int page = 0;
 | 
|---|
 | 37 |                         if (req.QueryString ["page"] != null) {
 | 
|---|
 | 38 |                                 int.TryParse (req.QueryString ["page"], out page);
 | 
|---|
 | 39 |                         }
 | 
|---|
 | 40 | 
 | 
|---|
 | 41 |                         int firstEntry = page * rowsPerPage;
 | 
|---|
 | 42 | 
 | 
|---|
 | 43 |                         Players playersList = PersistentContainer.Instance.Players;
 | 
|---|
 | 44 | 
 | 
|---|
 | 45 |                         List<JSONObject> playerList = new List<JSONObject> ();
 | 
|---|
 | 46 | 
 | 
|---|
 | 47 |                         foreach (string sid in playersList.SteamIDs) {
 | 
|---|
 | 48 |                 Player p = playersList [sid, false];
 | 
|---|
 | 49 | 
 | 
|---|
 | 50 |                 ulong player_steam_ID = 0L;
 | 
|---|
 | 51 |                 if (!ulong.TryParse (sid, out player_steam_ID))
 | 
|---|
 | 52 |                     player_steam_ID = 0L;
 | 
|---|
 | 53 | 
 | 
|---|
 | 54 |                 if ((player_steam_ID == user.SteamID) || bViewAll) {
 | 
|---|
 | 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));
 | 
|---|
 | 59 | 
 | 
|---|
 | 60 |                     JSONObject pJson = new JSONObject ();
 | 
|---|
 | 61 |                     pJson.Add("steamid", new JSONString (sid));
 | 
|---|
 | 62 |                                         pJson.Add("entityid", new JSONNumber (p.EntityID));
 | 
|---|
 | 63 |                     pJson.Add("ip", new JSONString (p.IP));
 | 
|---|
 | 64 |                     pJson.Add("name", new JSONString (p.Name));
 | 
|---|
 | 65 |                     pJson.Add("online", new JSONBoolean (p.IsOnline));
 | 
|---|
 | 66 |                     pJson.Add("position", pos);
 | 
|---|
 | 67 | 
 | 
|---|
 | 68 |                                         pJson.Add ("totalplaytime", new JSONNumber (p.TotalPlayTime));
 | 
|---|
| [309] | 69 |                                         pJson.Add ("lastonline", new JSONString (p.LastOnline.ToUniversalTime ().ToString ("yyyy-MM-ddTHH:mm:ssZ")));
 | 
|---|
| [279] | 70 |                                         pJson.Add ("ping", new JSONNumber (p.IsOnline ? p.ClientInfo.ping : -1));
 | 
|---|
 | 71 | 
 | 
|---|
 | 72 |                                         JSONBoolean banned = null;
 | 
|---|
 | 73 |                                         if (admTools != null) {
 | 
|---|
 | 74 |                                                 banned = new JSONBoolean (admTools.IsBanned (sid));
 | 
|---|
 | 75 |                                         } else {
 | 
|---|
 | 76 |                                                 banned = new JSONBoolean (false);
 | 
|---|
 | 77 |                                         }
 | 
|---|
 | 78 |                                         pJson.Add ("banned", banned);
 | 
|---|
 | 79 | 
 | 
|---|
 | 80 |                                         playerList.Add (pJson);
 | 
|---|
 | 81 |                 }
 | 
|---|
 | 82 |             }
 | 
|---|
 | 83 | 
 | 
|---|
 | 84 |                         IEnumerable<JSONObject> list = playerList;
 | 
|---|
 | 85 | 
 | 
|---|
 | 86 |                         foreach (string key in req.QueryString.AllKeys) {
 | 
|---|
 | 87 |                                 if (!string.IsNullOrEmpty (key) && key.StartsWith ("filter[")) {
 | 
|---|
 | 88 |                                         string filterCol = key.Substring (key.IndexOf ('[') + 1);
 | 
|---|
 | 89 |                                         filterCol = filterCol.Substring (0, filterCol.Length - 1);
 | 
|---|
 | 90 |                                         string filterVal = req.QueryString.Get (key).Trim ();
 | 
|---|
 | 91 | 
 | 
|---|
 | 92 |                                         list = ExecuteFilter (list, filterCol, filterVal);
 | 
|---|
 | 93 |                                 }
 | 
|---|
 | 94 |                         }
 | 
|---|
 | 95 | 
 | 
|---|
 | 96 |                         int totalAfterFilter = list.Count ();
 | 
|---|
 | 97 | 
 | 
|---|
 | 98 |                         foreach (string key in req.QueryString.AllKeys) {
 | 
|---|
 | 99 |                                 if (!string.IsNullOrEmpty (key) && key.StartsWith ("sort[")) {
 | 
|---|
 | 100 |                                         string sortCol = key.Substring (key.IndexOf ('[') + 1);
 | 
|---|
 | 101 |                                         sortCol = sortCol.Substring (0, sortCol.Length - 1);
 | 
|---|
 | 102 |                                         string sortVal = req.QueryString.Get (key);
 | 
|---|
 | 103 | 
 | 
|---|
 | 104 |                                         list = ExecuteSort (list, sortCol, sortVal == "0");
 | 
|---|
 | 105 |                                 }
 | 
|---|
 | 106 |                         }
 | 
|---|
 | 107 | 
 | 
|---|
 | 108 |                         list = list.Skip (firstEntry);
 | 
|---|
 | 109 |                         list = list.Take (rowsPerPage);
 | 
|---|
 | 110 | 
 | 
|---|
 | 111 | 
 | 
|---|
 | 112 |                         JSONArray playersJsResult = new JSONArray ();
 | 
|---|
 | 113 |                         foreach (JSONObject jsO in list) {
 | 
|---|
 | 114 |                                 playersJsResult.Add (jsO);
 | 
|---|
 | 115 |                         }
 | 
|---|
 | 116 | 
 | 
|---|
 | 117 |                         JSONObject result = new JSONObject ();
 | 
|---|
 | 118 |                         result.Add ("total", new JSONNumber (totalAfterFilter));
 | 
|---|
 | 119 |                         result.Add ("totalUnfiltered", new JSONNumber (playerList.Count));
 | 
|---|
 | 120 |                         result.Add ("firstResult", new JSONNumber (firstEntry));
 | 
|---|
 | 121 |                         result.Add ("players", playersJsResult);
 | 
|---|
 | 122 | 
 | 
|---|
 | 123 |                         WriteJSON (resp, result);
 | 
|---|
 | 124 |                 }
 | 
|---|
 | 125 | 
 | 
|---|
 | 126 |                 private IEnumerable<JSONObject> ExecuteFilter (IEnumerable<JSONObject> _list, string _filterCol, string _filterVal) {
 | 
|---|
 | 127 |                         if (_list.Count () == 0) {
 | 
|---|
 | 128 |                                 return _list;
 | 
|---|
 | 129 |                         }
 | 
|---|
 | 130 | 
 | 
|---|
 | 131 |                         if (_list.First ().ContainsKey (_filterCol)) {
 | 
|---|
 | 132 |                                 Type colType = _list.First () [_filterCol].GetType ();
 | 
|---|
 | 133 |                                 if (colType == typeof(JSONNumber)) {
 | 
|---|
 | 134 |                                         return ExecuteNumberFilter (_list, _filterCol, _filterVal);
 | 
|---|
 | 135 |                                 } else if (colType == typeof(JSONBoolean)) {
 | 
|---|
 | 136 |                                         bool value = _filterVal.Trim ().ToLower () == "true";
 | 
|---|
 | 137 |                                         return _list.Where (line => (line [_filterCol] as JSONBoolean).GetBool () == value);
 | 
|---|
 | 138 |                                 } else if (colType == typeof(JSONString)) {
 | 
|---|
 | 139 |                                         // regex-match whole ^string$, replace * by .*, ? by .?, + by .+
 | 
|---|
 | 140 |                                         _filterVal = _filterVal.Replace ("*", ".*").Replace ("?", ".?").Replace ("+", ".+");
 | 
|---|
 | 141 |                                         _filterVal = "^" + _filterVal + "$";
 | 
|---|
 | 142 |                                         //Log.Out ("GetPlayerList: Filter on String with Regex '" + _filterVal + "'");
 | 
|---|
 | 143 |                                         Regex matcher = new Regex (_filterVal, RegexOptions.IgnoreCase);
 | 
|---|
 | 144 |                                         return _list.Where (line => matcher.IsMatch ((line [_filterCol] as JSONString).GetString ()));
 | 
|---|
 | 145 |                                 }
 | 
|---|
 | 146 |                         }
 | 
|---|
 | 147 |                         return _list;
 | 
|---|
 | 148 |                 }
 | 
|---|
 | 149 | 
 | 
|---|
 | 150 | 
 | 
|---|
 | 151 |                 private IEnumerable<JSONObject> ExecuteNumberFilter (IEnumerable<JSONObject> _list, string _filterCol, string _filterVal) {
 | 
|---|
 | 152 |                         // allow value (exact match), =, ==, >=, >, <=, <
 | 
|---|
 | 153 |                         Match filterMatch = numberFilterMatcher.Match (_filterVal);
 | 
|---|
 | 154 |                         if (filterMatch.Success) {
 | 
|---|
 | 155 |                                 double value = Utils.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 |                                 return _list.Where (delegate(JSONObject line) {
 | 
|---|
 | 183 |                                         double objVal = (line [_filterCol] as JSONNumber).GetDouble ();
 | 
|---|
 | 184 |                                         switch (matchType) {
 | 
|---|
 | 185 |                                         case NumberMatchType.Greater:
 | 
|---|
 | 186 |                                                 return objVal > value;
 | 
|---|
 | 187 |                                         case NumberMatchType.GreaterEqual:
 | 
|---|
 | 188 |                                                 return objVal >= value;
 | 
|---|
 | 189 |                                         case NumberMatchType.Lesser:
 | 
|---|
 | 190 |                                                 return objVal < value;
 | 
|---|
 | 191 |                                         case NumberMatchType.LesserEqual:
 | 
|---|
 | 192 |                                                 return objVal <= value;
 | 
|---|
 | 193 |                                         case NumberMatchType.Equal:
 | 
|---|
 | 194 |                                         default:
 | 
|---|
 | 195 |                                                 return NearlyEqual (objVal, value, epsilon);
 | 
|---|
 | 196 |                                         }
 | 
|---|
 | 197 |                                 });
 | 
|---|
 | 198 |                         } else {
 | 
|---|
 | 199 |                                 Log.Out ("GetPlayerList: ignoring invalid filter for number-column '{0}': '{1}'", _filterCol, _filterVal);
 | 
|---|
 | 200 |                         }
 | 
|---|
 | 201 |                         return _list;
 | 
|---|
 | 202 |                 }
 | 
|---|
 | 203 | 
 | 
|---|
 | 204 | 
 | 
|---|
 | 205 |                 private IEnumerable<JSONObject> ExecuteSort (IEnumerable<JSONObject> _list, string _sortCol, bool _ascending) {
 | 
|---|
 | 206 |                         if (_list.Count () == 0) {
 | 
|---|
 | 207 |                                 return _list;
 | 
|---|
 | 208 |                         }
 | 
|---|
 | 209 | 
 | 
|---|
 | 210 |                         if (_list.First ().ContainsKey (_sortCol)) {
 | 
|---|
 | 211 |                                 Type colType = _list.First () [_sortCol].GetType ();
 | 
|---|
 | 212 |                                 if (colType == typeof(JSONNumber)) {
 | 
|---|
 | 213 |                                         if (_ascending) {
 | 
|---|
 | 214 |                                                 return _list.OrderBy (line => (line [_sortCol] as JSONNumber).GetDouble ());
 | 
|---|
 | 215 |                                         } else {
 | 
|---|
 | 216 |                                                 return _list.OrderByDescending (line => (line [_sortCol] as JSONNumber).GetDouble ());
 | 
|---|
 | 217 |                                         }
 | 
|---|
 | 218 |                                 } else if (colType == typeof(JSONBoolean)) {
 | 
|---|
 | 219 |                                         if (_ascending) {
 | 
|---|
 | 220 |                                                 return _list.OrderBy (line => (line [_sortCol] as JSONBoolean).GetBool ());
 | 
|---|
 | 221 |                                         } else {
 | 
|---|
 | 222 |                                                 return _list.OrderByDescending (line => (line [_sortCol] as JSONBoolean).GetBool ());
 | 
|---|
 | 223 |                                         }
 | 
|---|
 | 224 |                                 } else {
 | 
|---|
 | 225 |                                         if (_ascending) {
 | 
|---|
 | 226 |                                                 return _list.OrderBy (line => line [_sortCol].ToString ());
 | 
|---|
 | 227 |                                         } else {
 | 
|---|
 | 228 |                                                 return _list.OrderByDescending (line => line [_sortCol].ToString ());
 | 
|---|
 | 229 |                                         }
 | 
|---|
 | 230 |                                 }
 | 
|---|
 | 231 |                         }
 | 
|---|
 | 232 |                         return _list;
 | 
|---|
 | 233 |                 }
 | 
|---|
 | 234 | 
 | 
|---|
 | 235 | 
 | 
|---|
 | 236 |                 private bool NearlyEqual(double a, double b, double epsilon)
 | 
|---|
 | 237 |                 {
 | 
|---|
 | 238 |                         double absA = Math.Abs(a);
 | 
|---|
 | 239 |                         double absB = Math.Abs(b);
 | 
|---|
 | 240 |                         double diff = Math.Abs(a - b);
 | 
|---|
 | 241 | 
 | 
|---|
 | 242 |                         if (a == b)
 | 
|---|
 | 243 |                         { // shortcut, handles infinities
 | 
|---|
 | 244 |                                 return true;
 | 
|---|
 | 245 |                         } 
 | 
|---|
 | 246 |                         else if (a == 0 || b == 0 || diff < Double.Epsilon) 
 | 
|---|
 | 247 |                         {
 | 
|---|
 | 248 |                                 // a or b is zero or both are extremely close to it
 | 
|---|
 | 249 |                                 // relative error is less meaningful here
 | 
|---|
 | 250 |                                 return diff < epsilon;
 | 
|---|
 | 251 |                         }
 | 
|---|
 | 252 |                         else
 | 
|---|
 | 253 |                         { // use relative error
 | 
|---|
 | 254 |                                 return diff / (absA + absB) < epsilon;
 | 
|---|
 | 255 |                         }
 | 
|---|
 | 256 |                 }
 | 
|---|
 | 257 | 
 | 
|---|
 | 258 |         }
 | 
|---|
 | 259 | }
 | 
|---|
 | 260 | 
 | 
|---|