/// <summary> /// Converts a received parameter array from the client string to a keyValue pair dictionary /// </summary> /// <param name="parts">The array of data from the client</param> /// <returns></returns> private static Dictionary <string, string> ConvertToKeyValue(string[] parts) { Dictionary <string, string> Dic = new NiceDictionary <string, string>(); try { for (int i = 0; i < parts.Length; i += 2) { if (!Dic.ContainsKey(parts[i])) { Dic.Add(parts[i], parts[i + 1]); } } } catch (IndexOutOfRangeException) { } return(Dic); }
/// <summary> /// Queries the database, and returns a result set /// </summary> /// <param name="Sql">The SQL Statement to run on the database</param> /// <param name="Params">A list of sql params to add to the command</param> /// <returns></returns> public List <Dictionary <string, object> > Query(string Sql, List <DbParameter> Params) { // Create our Rows result List <Dictionary <string, object> > Rows = new List <Dictionary <string, object> >(); // Increase Query Count NumQueries++; // Create the SQL Command using (DbCommand Command = this.CreateCommand(Sql)) { // Add params foreach (DbParameter Param in Params) { Command.Parameters.Add(Param); } // Execute the query using (DbDataReader Reader = Command.ExecuteReader()) { // If we have rows, add them to the list if (Reader.HasRows) { // Add each row to the rows list while (Reader.Read()) { NiceDictionary <string, object> Row = new NiceDictionary <string, object>(Reader.FieldCount); for (int i = 0; i < Reader.FieldCount; ++i) { Row.Add(Reader.GetName(i), Reader.GetValue(i)); } Rows.Add(Row); } } // Cleanup Reader.Close(); } } // Return Rows return(Rows); }
/// <summary> /// Converts a received parameter array from the client string to a keyValue pair dictionary /// </summary> /// <param name="parts">The array of data from the client</param> /// <returns></returns> private static Dictionary<string, string> ConvertToKeyValue(string[] parts) { Dictionary<string, string> Dic = new NiceDictionary<string, string>(); try { for (int i = 0; i < parts.Length; i += 2) { if (!Dic.ContainsKey(parts[i])) Dic.Add(parts[i], parts[i + 1]); } } catch (IndexOutOfRangeException) { } return Dic; }
/// <summary> /// Initializes a new Snapshot, with the specified Date it was Posted /// </summary> /// <param name="Snapshot">The snapshot source</param> public Snapshot(string Snapshot, IPAddress ServerIp = null) { // Set some internal variables this.ServerIp = ServerIp ?? IPAddress.Loopback; this.Players = new List<Player>(); this.DataString = Snapshot.Trim(); string[] Data = DataString.Split('\\'); Snapshot = null; // Check for invalid snapshot string. All snapshots have at least 36 data pairs, // and has an Even number of data sectors. if (Data.Length < 36 || Data.Length % 2 != 0) throw new InvalidDataException("Snapshot does not contain at least 36 elements, or contains an odd number of elements"); // Assign server name and prefix this.ServerPrefix = Data[0]; this.ServerName = Data[1]; // Determine if we are central update. the "cdb_update" variable must be the LAST sector in snapshot int Mode; if (Data[Data.Length - 2] == "cdb_update" && Int32.TryParse(Data[Data.Length - 1], out Mode) && Mode.InRange(1, 2)) { this.SnapshotMode = (Mode == 2) ? SnapshotMode.Minimal : SnapshotMode.FullSync; } // Setup our data dictionary's. We use NiceDictionary so we can easily determine missing keys in the log file NiceDictionary<string, string> StandardData = new NiceDictionary<string, string>(16); NiceDictionary<string, string> PlayerData = new NiceDictionary<string, string>(); Dictionary<int, int> KillData = new Dictionary<int, int>(); // Wrap parsing Key/Value snapshot data in a try block! try { // Convert our standard data into key => value pairs for (int i = 2; i < Data.Length; i += 2) { // Format: "DataKey_PlayerIndex". PlayerIndex is NOT the Player Id string[] Parts = Data[i].Split('_'); if (Parts.Length == 1) { // Add to the Standard keys StandardData.Add(Data[i], Data[i + 1]); // Are we at the End of File? If so stop here if (Parts[0] == "EOF") { // Make sure to save that last players stats!! if (PlayerData.Count != 0) { AddPlayer(new Player(PlayerData, KillData)); PlayerData = null; KillData = null; } break; } } // If the item key is "pID", then we have a new player record else if (Parts[0] == "pID") { // If we have data, complete this player and start anew if (PlayerData.Count != 0) { AddPlayer(new Player(PlayerData, KillData)); PlayerData.Clear(); KillData.Clear(); } // Add new PID PlayerData.Add(Parts[0], Data[i + 1]); } else if (Parts[0] == "mvks") // Skip mvks... kill data only needs processed once (mvns) continue; else if (Parts[0] == "mvns") // Player kill data KillData.Add(Int32.Parse(Data[i + 1]), Int32.Parse(Data[i + 3])); else PlayerData.Add(Parts[0], Data[i + 1]); } } catch (Exception e) { throw new InvalidDataException("Error assigning Key => value pairs. See InnerException", e); } // Make sure we have a completed snapshot if (!StandardData.ContainsKey("EOF")) throw new InvalidDataException("No End of File element was found, Snapshot assumed to be incomplete."); // Try and set internal GameResult variables try { // Server data this.ServerPort = Int32.Parse(StandardData["gameport"]); this.QueryPort = Int32.Parse(StandardData["queryport"]); // Map Data this.MapName = StandardData["mapname"]; this.MapId = Int32.Parse(StandardData["mapid"]); this.RoundStartTime = (int)Convert.ToDouble(StandardData["mapstart"], CultureInfo.InvariantCulture.NumberFormat); this.RoundEndTime = (int)Convert.ToDouble(StandardData["mapend"], CultureInfo.InvariantCulture.NumberFormat); // Misc Data this.GameMode = Int32.Parse(StandardData["gm"]); this.Mod = StandardData["v"]; // bf2 mod replaced the version key, since we dont care the version anyways this.PlayersConnected = Int32.Parse(StandardData["pc"]); // Army Data... There is no RWA key if there was no winner... this.WinningTeam = Int32.Parse(StandardData["win"]); // Temp this.WinningArmyId = (StandardData.ContainsKey("rwa")) ? Int32.Parse(StandardData["rwa"]) : -1; this.Team1ArmyId = Int32.Parse(StandardData["ra1"]); this.Team1Tickets = Int32.Parse(StandardData["rs1"]); this.Team2ArmyId = Int32.Parse(StandardData["ra2"]); this.Team2Tickets = Int32.Parse(StandardData["rs2"]); } catch (Exception e) { throw new InvalidDataException("Error assigning GameResult variables. See InnerException", e); } // Wrap this in a try-catch block, because we want to be able to view GameResult // data, even if the database is offline try { // Dispose when done! using (StatsDatabase Driver = new StatsDatabase()) { // Check for custom map, with no ID (Not defined in Constants.py) if (this.MapId == 99) { // Check for existing map data var Rows = Driver.Query("SELECT id FROM mapinfo WHERE name=@P0", this.MapName); if (Rows.Count == 0) { // Create new MapId. Id's 700 - 1000 are reserved for unknown maps in the Constants.py file // There should never be more then 300 unknown map id's, considering 1001 is the start of KNOWN // Custom mod map id's. If we are at 1000 now, then we are in trouble :S this.MapId = Driver.ExecuteScalar<int>("SELECT COALESCE(MAX(id), 699) FROM mapinfo WHERE id BETWEEN 700 AND 999") + 1; if (this.MapId == 1000) throw new Exception("Maximum unknown custom mapid has been reached. Please add this map's mapid to the Constants.py"); // Insert map data, so we dont lose this mapid we generated Driver.Execute("INSERT INTO mapinfo(id, name, custom) VALUES (@P0, @P1, @P2)", this.MapId, this.MapName, 1); } else this.MapId = Int32.Parse(Rows[0]["id"].ToString()); } // Set whether or not this data is already been processed. The OR condition is because i goofed in early updates // and set the timestamp to the RoundStart instead of the RoundEnd like i should have this.IsProcessed = Driver.ExecuteScalar<int>( "SELECT COUNT(*) FROM round_history WHERE mapid=@P0 AND time=@P1 AND (timestamp=@P2 OR timestamp=@P3)", this.MapId, this.RoundTime.Seconds, this.RoundEndTime, this.RoundStartTime ) > 0; } } catch(DbConnectException) { this.IsProcessed = false; } // Indicate whether we are a custom map this.IsCustomMap = (this.MapId >= 700 || this.MapId == 99); }