diff --git a/NED.WoT.BattleResults.Client/Components/Pages/Index.razor b/NED.WoT.BattleResults.Client/Components/Pages/Index.razor index 3e69aff..8efee28 100644 --- a/NED.WoT.BattleResults.Client/Components/Pages/Index.razor +++ b/NED.WoT.BattleResults.Client/Components/Pages/Index.razor @@ -1,6 +1,7 @@ @page "/" @using System.Collections.ObjectModel; @using System.Collections.Specialized; +@using System.Diagnostics @inject BattleReportService BattleResultService; @inject SettingService SettingService; @@ -61,7 +62,6 @@ # - @{ var wins = context.Items.Count(x => x.IsWin(SettingService.Settings)); @@ -98,13 +98,19 @@ - @if (context.Error != null) + @if (_loading) + { + + + + } + else if (context.Error != null) { @context.Error } else { - var group = _reports.Where(x => TableFilter(x)).GroupBy(x => x.Group).First(x => x.Any(i => i.FileName == context.FileName)).OrderBy(x => x.MatchStart).ToList(); + var group = _filteredReports.GroupBy(x => x.Group).First(x => x.Any(i => i.FileName == context.FileName)).OrderBy(x => x.MatchStart).ToList(); var index = group.FindIndex(x => x.FileName == context.FileName) + 1; Team ownTeam = context.GetOwnTeam(SettingService.Settings); @@ -180,7 +186,9 @@ var percentage_color = winPercentage > 50 ? "Win" : winPercentage < 40 ? "Lose" : "Draw"; }
- Totaal @_filteredReports.Count gevechten @winPercentage.ToString("0.00")% + Totaal @_filteredReports.Count() gevechten @winPercentage.ToString("0.00")% + + @wins Gewonnen @@ -193,7 +201,12 @@
- + + @if (_loadingTime != TimeSpan.Zero) + { + @_loadingTime.ToString(@"hh\:mm\:ss\.fff") + } + @@ -203,11 +216,12 @@ private bool _loading = true; private string searchString = ""; private bool _hideNormalMatchMaking = true; + private TimeSpan _loadingTime = TimeSpan.Zero; private bool _showOnlyUnkown = false; private List _reports = new List(); - private List _filteredReports => _reports.Where(x => TableFilter(x)).ToList(); + private IEnumerable _filteredReports => _reports.Where(x => TableFilter(x)); private TableGroupDefinition _groupDefinition = new() { Indentation = false, @@ -217,32 +231,48 @@ protected override async Task OnInitializedAsync() { + long timestamp = 0; + BattleResultService.LoadingBattleReportsStarted += async (s, e) => { await InvokeAsync(() => { _reports = new List(); _loading = true; + timestamp = Stopwatch.GetTimestamp(); StateHasChanged(); }); }; - BattleResultService.BattleReportAdded += (s, e) => + BattleResultService.BattleReportAdded += async (s, e) => { - _reports.Add(e.Report); + await InvokeAsync(() => + { + _reports.Add(e.Report); + if (_loading) + { + _loadingTime = Stopwatch.GetElapsedTime(timestamp); + } + StateHasChanged(); + }); }; - BattleResultService.BattleReportRemoved += (s, e) => + BattleResultService.BattleReportRemoved += async (s, e) => { - _reports.Remove(e.Report); + await InvokeAsync(() => + { + _reports.Remove(e.Report); + StateHasChanged(); + }); }; BattleResultService.LoadingBattleReportsFinished += async (s, e) => { await InvokeAsync(() => { - _reports = BattleResultService.BattleReports.OrderByDescending(x => x.Value.MatchStart).Select(x => x.Value).ToList(); + _reports = BattleResultService.BattleReports.Select(x => x.Value).OrderByDescending(x => x.MatchStart).ToList(); _loading = false; + _loadingTime = Stopwatch.GetElapsedTime(timestamp); StateHasChanged(); }); }; diff --git a/NED.WoT.BattleResults.Client/Components/Shared/ClanMembersDialog.razor b/NED.WoT.BattleResults.Client/Components/Shared/ClanMembersDialog.razor index 476815f..4580ae1 100644 --- a/NED.WoT.BattleResults.Client/Components/Shared/ClanMembersDialog.razor +++ b/NED.WoT.BattleResults.Client/Components/Shared/ClanMembersDialog.razor @@ -21,7 +21,7 @@ @code { [CascadingParameter] MudDialogInstance MudDialog { get; set; } - [Parameter] public List Reports { get; set; } = []; + [Parameter] public IEnumerable Reports { get; set; } = []; private IEnumerable GetClanMembers() diff --git a/NED.WoT.BattleResults.Client/Components/Shared/SettingsDialog.razor b/NED.WoT.BattleResults.Client/Components/Shared/SettingsDialog.razor index 398be91..9cfff14 100644 --- a/NED.WoT.BattleResults.Client/Components/Shared/SettingsDialog.razor +++ b/NED.WoT.BattleResults.Client/Components/Shared/SettingsDialog.razor @@ -29,6 +29,9 @@ + + Ga naar app settings @@ -59,7 +62,8 @@ ClanAbbreviation = SettingService.Settings.ClanAbbreviation, PlayerName = SettingService.Settings.PlayerName, SingleBattleResultOpenedOnly = SettingService.Settings.SingleBattleResultOpenedOnly, - OnlyHighlistOwnMatches = SettingService.Settings.OnlyHighlistOwnMatches + OnlyHighlistOwnMatches = SettingService.Settings.OnlyHighlistOwnMatches, + StartupUpdateOnEveryReport = SettingService.Settings.StartupUpdateOnEveryReport }; } @@ -77,6 +81,7 @@ settings.PlayerName = modal.PlayerName; settings.SingleBattleResultOpenedOnly = modal.SingleBattleResultOpenedOnly; settings.OnlyHighlistOwnMatches = modal.OnlyHighlistOwnMatches; + settings.StartupUpdateOnEveryReport = modal.StartupUpdateOnEveryReport; SettingService.Save(settings); @@ -103,5 +108,6 @@ public bool OnlyHighlistOwnMatches { get; set; } + public bool StartupUpdateOnEveryReport { get; set; } } } diff --git a/NED.WoT.BattleResults.Client/MauiProgram.cs b/NED.WoT.BattleResults.Client/MauiProgram.cs index 29807bf..c7ae3c6 100644 --- a/NED.WoT.BattleResults.Client/MauiProgram.cs +++ b/NED.WoT.BattleResults.Client/MauiProgram.cs @@ -10,7 +10,7 @@ public static class MauiProgram { public static MauiApp CreateMauiApp() { - var builder = MauiApp.CreateBuilder(); + MauiAppBuilder builder = MauiApp.CreateBuilder(); builder .UseMauiApp() .ConfigureFonts(fonts => diff --git a/NED.WoT.BattleResults.Client/Models/BattleReport.cs b/NED.WoT.BattleResults.Client/Models/BattleReport.cs index 4640941..27306e7 100644 --- a/NED.WoT.BattleResults.Client/Models/BattleReport.cs +++ b/NED.WoT.BattleResults.Client/Models/BattleReport.cs @@ -31,13 +31,13 @@ public MarkupString MatchDurationDisplay { get { - var duration = string.Empty; + string duration = string.Empty; if (MatchDuration.HasValue) { - var time = new TimeSpan(MatchDuration.Value * TimeSpan.TicksPerSecond); - var minutes = time.Minutes.ToString(); + TimeSpan time = new TimeSpan(MatchDuration.Value * TimeSpan.TicksPerSecond); + string minutes = time.Minutes.ToString(); if (minutes.Length == 1) minutes = "  " + minutes; - var seconds = time.Seconds.ToString(); + string seconds = time.Seconds.ToString(); if (seconds.Length == 1) seconds = "  " + seconds; return (MarkupString)$"{minutes}m {seconds}s"; @@ -58,7 +58,7 @@ public string Group { get { - var daysAgo = (DateTime.Now.Date - MatchStart.Date).Days; + int daysAgo = (DateTime.Now.Date - MatchStart.Date).Days; if (MatchStart.Hour <= 2) { daysAgo += 1; @@ -157,7 +157,7 @@ public bool IsOwnTeam(Settings settings) public string GetResult(string map) { - var lines = Players.Where(x => x.Name != null).OrderByDescending(x => x.ExperienceEarned).Select(x => x.Name).ToList(); + List lines = Players.Where(x => x.Name != null).OrderByDescending(x => x.ExperienceEarned).Select(x => x.Name).ToList(); lines.Insert(0, $"{map} {(Number == 1 ? "I" : "II")}"); lines.Insert(1, ResultDisplay); diff --git a/NED.WoT.BattleResults.Client/Models/Settings.cs b/NED.WoT.BattleResults.Client/Models/Settings.cs index 1979414..c3797d2 100644 --- a/NED.WoT.BattleResults.Client/Models/Settings.cs +++ b/NED.WoT.BattleResults.Client/Models/Settings.cs @@ -9,5 +9,6 @@ public class Settings public bool ShowCopyNamesOnlyWhenClanMatches { get; set; } public bool SingleBattleResultOpenedOnly { get; set; } public bool OnlyHighlistOwnMatches { get; set; } + public bool StartupUpdateOnEveryReport { get; set; } } } diff --git a/NED.WoT.BattleResults.Client/Platforms/Windows/Package.appxmanifest b/NED.WoT.BattleResults.Client/Platforms/Windows/Package.appxmanifest index f8b85e0..2fa3d35 100644 --- a/NED.WoT.BattleResults.Client/Platforms/Windows/Package.appxmanifest +++ b/NED.WoT.BattleResults.Client/Platforms/Windows/Package.appxmanifest @@ -6,7 +6,7 @@ xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" IgnorableNamespaces="uap rescap"> - + diff --git a/NED.WoT.BattleResults.Client/Services/BattleReportMapper.cs b/NED.WoT.BattleResults.Client/Services/BattleReportMapper.cs index a83f73d..4bffc36 100644 --- a/NED.WoT.BattleResults.Client/Services/BattleReportMapper.cs +++ b/NED.WoT.BattleResults.Client/Services/BattleReportMapper.cs @@ -1,154 +1,170 @@ -using NED.WoT.BattleResults.Client.Models; +using System.Globalization; +using System.Runtime.InteropServices; +using System.Text.Json.Nodes; + +using NED.WoT.BattleResults.Client.Models; using NED.WoT.BattleResults.Client.Services; -using System.Globalization; -using System.Text.Json.Nodes; +namespace NED.WoT.BattleResults.Client.Data; -namespace NED.WoT.BattleResults.Client.Data +public class BattleReportMapper { - public class BattleReportMapper - { - protected BattleReportMapper() { } + protected BattleReportMapper() { } - private static CultureInfo CultureInfo => CultureInfo.InvariantCulture; + private static CultureInfo CultureInfo => CultureInfo.InvariantCulture; - public static BattleReport Map(JsonObject replay, JsonArray stats, Settings settings) + public static BattleReport Map(JsonObject replay, JsonArray stats, Settings settings) + { + if (!DateTime.TryParseExact(replay["dateTime"].GetValue(), "dd.MM.yyyy HH:mm:ss", CultureInfo, DateTimeStyles.None, out DateTime matchStart)) { - var datetime = replay["dateTime"].GetValue(); - if (!DateTime.TryParseExact(datetime, "dd.MM.yyyy HH:mm:ss", CultureInfo, DateTimeStyles.None, out DateTime matchStart)) - { - throw new Exception($"Could not parse match datetime '{datetime}' with culture '{CultureInfo.Name}'. Current culture is '{CultureInfo.CurrentCulture.Name}'."); - } + throw new Exception($"Could not parse match datetime '{replay["dateTime"].GetValue()}' with culture '{CultureInfo.Name}'. Current culture is '{CultureInfo.CurrentCulture.Name}'."); + } - var game = new BattleReport - { - MapName = MapNameResolver.GetMapName(replay["mapDisplayName"].GetValue()), - MatchStart = matchStart, - Team1 = new Team() - { - Number = 1 - }, - Team2 = new Team() - { - Number = 2 - } - }; - - if (stats != null) - { - game.FinishReason = stats[0]["common"]["finishReason"].GetValue(); - game.MatchDuration = stats[0]["common"]["duration"].GetValue(); - game.Team1.Health = stats[0]["common"]["teamHealth"]["1"].GetValue(); - game.Team1.IsWinner = stats[0]["common"]["winnerTeam"].GetValue() == game.Team1.Number; - game.Team2.Health = stats[0]["common"]["teamHealth"]["2"].GetValue(); - game.Team2.IsWinner = stats[0]["common"]["winnerTeam"].GetValue() == game.Team2.Number; - - foreach (var vehicle in stats[0]["vehicles"].AsObject()) - { - var vehicleData = vehicle.Value.AsArray()[0].AsObject(); - var playerData = stats[1][vehicle.Key].AsObject(); - MapPlayerData(game, playerData, vehicleData, settings); - } - } - else + BattleReport game = new() + { + MapName = MapNameResolver.GetMapName(replay["mapDisplayName"].GetValue()), + MatchStart = matchStart, + Team1 = new Team { Number = 1 }, + Team2 = new Team { Number = 2 } + }; + + if (stats != null) + { + JsonNode commonStats = stats[0]["common"]; + game.FinishReason = commonStats["finishReason"].GetValue(); + game.MatchDuration = commonStats["duration"].GetValue(); + game.Team1.Health = commonStats["teamHealth"]["1"].GetValue(); + game.Team1.IsWinner = commonStats["winnerTeam"].GetValue() == game.Team1.Number; + game.Team2.Health = commonStats["teamHealth"]["2"].GetValue(); + game.Team2.IsWinner = commonStats["winnerTeam"].GetValue() == game.Team2.Number; + + foreach (KeyValuePair vehicle in stats[0]["vehicles"].AsObject()) { - foreach (var vehicle in replay["vehicles"].AsObject()) - { - var playerData = vehicle.Value.AsObject(); - var vehicleData = stats?[0]?["vehicles"]?.AsObject()?[vehicle.Key]?[0]?.AsObject(); - MapPlayerData(game, playerData, vehicleData, settings); - } + MapPlayerData(game, stats[1][vehicle.Key].AsObject(), vehicle.Value.AsArray()[0].AsObject(), settings); } - - var mostMentionedClanTeam1 = game.Team1.Players.Where(x => !string.IsNullOrEmpty(x.Clan)).Select(x => x.Clan).GroupBy(x => x).MaxBy(x => x.Count()); - if (mostMentionedClanTeam1 != null) + } + else + { + foreach (KeyValuePair vehicle in replay["vehicles"].AsObject()) { - game.Team1.Abbreviation = mostMentionedClanTeam1.Count() >= Math.Max(game.Team1.Players.Count / 2, 4) ? mostMentionedClanTeam1.Key : "?"; + MapPlayerData(game, vehicle.Value.AsObject(), stats?[0]?["vehicles"]?.AsObject()?[vehicle.Key]?[0]?.AsObject(), settings); } + } - var mostMentionedClanTeam2 = game.Team2.Players.Where(x => !string.IsNullOrEmpty(x.Clan)).Select(x => x.Clan).GroupBy(x => x).MaxBy(x => x.Count()); - if (mostMentionedClanTeam2 != null) - { - game.Team2.Abbreviation = mostMentionedClanTeam2.Count() >= Math.Max(game.Team2.Players.Count / 2, 4) ? mostMentionedClanTeam2.Key : "?"; - } + game.Team1.Abbreviation = GetMostMentionedClanAbbreviation(game.Team1); + game.Team2.Abbreviation = GetMostMentionedClanAbbreviation(game.Team2); - game.Team1.Result = GetResult(game.Team1.IsWinner, game.Team2.IsWinner); - game.Team2.Result = GetResult(game.Team2.IsWinner, game.Team1.IsWinner); + game.Team1.Result = GetResult(game.Team1.IsWinner, game.Team2.IsWinner); + game.Team2.Result = GetResult(game.Team2.IsWinner, game.Team1.IsWinner); - int playerCount = Math.Max(game.Team1.Players.Count, game.Team2.Players.Count); - EnrichAndSortPlayers(game.Team1, playerCount); - EnrichAndSortPlayers(game.Team2, playerCount); + int playerCount = Math.Max(game.Team1.Players.Count, game.Team2.Players.Count); + EnrichAndSortPlayers(game.Team1, playerCount); + EnrichAndSortPlayers(game.Team2, playerCount); - return game; + return game; + } + + private static string GetMostMentionedClanAbbreviation(Team team) + { + if (team.Players == null || team.Players.Count == 0) + { + return "?"; } - private static void EnrichAndSortPlayers(Team team, int playerCount) + Dictionary clanCounts = []; + string mostMentionedClan = "?"; + int maxMentions = 0; + int mentionThreshold = Math.Max(team.Players.Count / 2, 4); + + foreach (var player in team.Players) { - var currentTeamPlayerCount = team.Players.Count; - for (int i = 0; i < playerCount - currentTeamPlayerCount; i++) + if (string.IsNullOrEmpty(player.Clan)) { - team.Players.Add(new Player()); + continue; } - team.Players = [.. team.Players.OrderByDescending(p => p.ExperienceEarned)]; - for (int i = 0; i < team.Players.Count; i++) + int count = clanCounts.GetValueOrDefault(player.Clan) + 1; + clanCounts[player.Clan] = count; + + if (count > maxMentions) { - team.Players[i].Number = i + 1; + mostMentionedClan = player.Clan; + maxMentions = count; } + } + + return maxMentions >= mentionThreshold ? mostMentionedClan : "?"; + } + + private static void EnrichAndSortPlayers(Team team, int playerCount) + { + int playersToAdd = playerCount - team.Players.Count; + if (playersToAdd > 0) + { + team.Players.AddRange(Enumerable.Repeat(new Player(), playersToAdd)); } - private static void MapPlayerData(BattleReport game, JsonObject playerData, JsonObject vehicleData, Settings settings) + Span playersSpan = CollectionsMarshal.AsSpan(team.Players); + playersSpan.Sort((p1, p2) => p2.ExperienceEarned.GetValueOrDefault().CompareTo(p1.ExperienceEarned.GetValueOrDefault())); + + for (int i = 0; i < playersSpan.Length; i++) { - var clan = playerData["clanAbbrev"].GetValue(); + playersSpan[i].Number = i + 1; + } + } - var team = playerData["team"].GetValue() == game.Team1.Number ? game.Team1 : game.Team2; + private static void MapPlayerData(BattleReport game, JsonObject playerData, JsonObject vehicleData, Settings settings) + { + string clan = playerData["clanAbbrev"].GetValue(); - var player = new Player() - { - Name = playerData["name"].GetValue(), - Clan = clan, - Vehicle = TankNameResolver.GetTankName(playerData["vehicleType"].GetValue()), - IsClanMember = settings.ClanAbbreviation?.ToLower() == clan?.ToLower() - }; + Team team = playerData["team"].GetValue() == game.Team1.Number ? game.Team1 : game.Team2; - if (vehicleData != null) - { - player.DamageDealt = vehicleData["damageDealt"]?.GetValue(); - player.DamageReceived = vehicleData["damageReceived"]?.GetValue(); - player.DamageBlocked = vehicleData["damageBlockedByArmor"]?.GetValue(); - player.Piercings = vehicleData["piercings"]?.GetValue(); - player.ExperienceEarned = vehicleData["xp"]?.GetValue(); - player.CreditsEarned = vehicleData["credits"]?.GetValue(); - player.Shots = vehicleData["shots"]?.GetValue(); - player.Kills = vehicleData["kills"]?.GetValue(); - player.IsTeamKiller = vehicleData["isTeamKiller"]?.ToString() == "1"; - player.CapturePoints = vehicleData["flagCapture"]?.GetValue(); - player.Health = vehicleData["health"]?.GetValue(); - player.DirectHits = vehicleData["directHits"]?.GetValue(); - player.Spotted = vehicleData["spotted"]?.GetValue(); - player.LifeTime = vehicleData["lifeTime"]?.GetValue(); - player.MaxHealth = vehicleData["maxHealth"]?.GetValue(); - player.DeathReason = vehicleData["deathReason"]?.GetValue(); - } + Player player = new() + { + Name = playerData["name"].GetValue(), + Clan = clan, + Vehicle = TankNameResolver.GetTankName(playerData["vehicleType"].GetValue()), + IsClanMember = settings.ClanAbbreviation?.ToLower() == clan?.ToLower() + }; - team.Players.Add(player); + if (vehicleData != null) + { + player.DamageDealt = vehicleData["damageDealt"]?.GetValue(); + player.DamageReceived = vehicleData["damageReceived"]?.GetValue(); + player.DamageBlocked = vehicleData["damageBlockedByArmor"]?.GetValue(); + player.Piercings = vehicleData["piercings"]?.GetValue(); + player.ExperienceEarned = vehicleData["xp"]?.GetValue(); + player.CreditsEarned = vehicleData["credits"]?.GetValue(); + player.Shots = vehicleData["shots"]?.GetValue(); + player.Kills = vehicleData["kills"]?.GetValue(); + player.IsTeamKiller = vehicleData["isTeamKiller"]?.ToString() == "1"; + player.CapturePoints = vehicleData["flagCapture"]?.GetValue(); + player.Health = vehicleData["health"]?.GetValue(); + player.DirectHits = vehicleData["directHits"]?.GetValue(); + player.Spotted = vehicleData["spotted"]?.GetValue(); + player.LifeTime = vehicleData["lifeTime"]?.GetValue(); + player.MaxHealth = vehicleData["maxHealth"]?.GetValue(); + player.DeathReason = vehicleData["deathReason"]?.GetValue(); } - private static Result GetResult(bool? team1IsWinner, bool? team2IsWinner) + team.Players.Add(player); + } + + private static Result GetResult(bool? team1IsWinner, bool? team2IsWinner) + { + if (team1IsWinner == true) { - if (team1IsWinner == true) - { - return Result.Win; - } - else if (team2IsWinner == true) - { - return Result.Lose; - } - else if (team1IsWinner == false && team2IsWinner == false) - { - return Result.Draw; - } - return Result.Unkown; + return Result.Win; + } + else if (team2IsWinner == true) + { + return Result.Lose; } + else if (team1IsWinner == false && team2IsWinner == false) + { + return Result.Draw; + } + + return Result.Unkown; } } diff --git a/NED.WoT.BattleResults.Client/Services/BattleReportService.cs b/NED.WoT.BattleResults.Client/Services/BattleReportService.cs index 346b36b..1477d38 100644 --- a/NED.WoT.BattleResults.Client/Services/BattleReportService.cs +++ b/NED.WoT.BattleResults.Client/Services/BattleReportService.cs @@ -1,14 +1,13 @@ -using MudBlazor; - -using NED.WoT.BattleResults.Client.Data; -using NED.WoT.BattleResults.Client.Models; - -using System.Collections; using System.Collections.Concurrent; using System.Text; using System.Text.Json; using System.Text.Json.Nodes; +using MudBlazor; + +using NED.WoT.BattleResults.Client.Data; +using NED.WoT.BattleResults.Client.Models; + namespace NED.WoT.BattleResults.Client.Services; public class BattleReportService @@ -19,6 +18,9 @@ public class BattleReportService private readonly SettingService _settingService; private FileSystemWatcher _watcher; + private static ReadOnlySpan ReplayStart => "{\"clientVersionFromXml\""u8; + private static ReadOnlySpan StatsStart => "[{\"personal\""u8; + public ConcurrentDictionary BattleReports { get; set; } = new ConcurrentDictionary(); public event EventHandler LoadingBattleReportsStarted; @@ -31,7 +33,6 @@ public class BattleReportService PropertyNameCaseInsensitive = true }; - public BattleReportService(ISnackbar snackbar, SettingService settingService) { _snackbar = snackbar; @@ -48,16 +49,29 @@ public void LoadBattleReports() LoadingBattleReportsStarted?.Invoke(this, new EventArgs()); - var directoryInfo = new DirectoryInfo(_settingService.Settings.WotReplayDirectory); - var files = directoryInfo.GetFiles(SEARCH_PATTERN) - .Where(x => x.LastWriteTime > _settingService.Settings.LoadBattlesSince) - .Where(x => !IsTempFile(x.Name)) - .OrderByDescending(x => x.CreationTime); + DirectoryInfo directoryInfo = new(_settingService.Settings.WotReplayDirectory); + List files = directoryInfo + .EnumerateFiles(SEARCH_PATTERN) + .Where(x => x.LastWriteTime > _settingService.Settings.LoadBattlesSince && !IsTempFile(x.Name)) + .OrderByDescending(x => x.CreationTime) + .ToList(); - Parallel.ForEach(files, (file) => + OrderablePartitioner partitioner = Partitioner.Create(files); + Parallel.ForEach(partitioner, new ParallelOptions { MaxDegreeOfParallelism = Environment.ProcessorCount }, file => { - var report = GetBattleReport(file); - AddBattleReport(file.Name, report); + try + { + BattleReport report = GetBattleReport(file); + + AddBattleReport(file.Name, report, _settingService.Settings.StartupUpdateOnEveryReport); + } + catch (Exception ex) + { + BattleReport report = new() + { + Error = $"Failed to process file {file.Name}: {ex.Message}" + }; + } }); LoadingBattleReportsFinished?.Invoke(this, new EventArgs()); @@ -105,7 +119,7 @@ private void OnCreated(object sender, FileSystemEventArgs e) LoadingBattleReportsStarted?.Invoke(this, new EventArgs()); - var report = GetBattleReport(new FileInfo(e.FullPath)); + BattleReport report = GetBattleReport(new FileInfo(e.FullPath)); if (report != null) { AddBattleReport(e.Name, report); @@ -119,10 +133,9 @@ private void OnCreated(object sender, FileSystemEventArgs e) LoadingBattleReportsFinished?.Invoke(this, new EventArgs()); } - private void OnDeleted(object sender, FileSystemEventArgs e) { - if (BattleReports.Remove(e.Name, out var report)) + if (BattleReports.Remove(e.Name, out BattleReport report)) { _snackbar.Add($"Replay bestand '{e.Name}' is verwijderd", Severity.Warning); BattleReportRemoved?.Invoke(this, new BattleReportRemovedEventArgs(report)); @@ -140,19 +153,19 @@ private BattleReport GetBattleReport(FileInfo file) try { - byte[] fileData = File.ReadAllBytes(file.FullName); - - int startIndex = FindSequenceIndex(fileData, [(byte)'{', (byte)'"']); - var replay = GetJsonFromFile(fileData, '{', '}', ref startIndex); - var stats = GetJsonFromFile(fileData, '[', ']', ref startIndex); + ReadOnlySpan fileData = File.ReadAllBytes(file.FullName); + int replayStartIndex = fileData.IndexOf(ReplayStart); + JsonObject replay = GetJsonFromFile(fileData, '{', '}', replayStartIndex); if (replay?["dateTime"] == null) { report.Error = $"Could not parse file: {file.Name}"; } else { - report = BattleReportMapper.Map(replay, stats, _settingService.Settings); + int statsStartIndex = fileData.IndexOf(StatsStart); + JsonArray stats = GetJsonFromFile(fileData, '[', ']', statsStartIndex); + report = BattleReportMapper.Map(replay, stats, _settingService.Settings); } } catch (Exception ex) @@ -165,22 +178,24 @@ private BattleReport GetBattleReport(FileInfo file) return report; } - private static T GetJsonFromFile(byte[] fileData, char start, char end, ref int startIndex) + private static T GetJsonFromFile(ReadOnlySpan fileData, char start, char end, int startIndex) { + if (startIndex == -1) + { + return default; + } + try { int jsonStarts = 0; int jsonEnds = 0; + int length = fileData.Length; - for (int i = startIndex; i < fileData.Length; i++) + for (int i = startIndex; i < length; i++) { if (fileData[i] == start) { jsonStarts++; - if (jsonStarts == 1) - { - startIndex = i; - } } else if (fileData[i] == end && jsonStarts > jsonEnds) { @@ -189,16 +204,12 @@ private static T GetJsonFromFile(byte[] fileData, char start, char end, ref i if (jsonStarts > 0 && jsonStarts == jsonEnds) { - string json = Encoding.UTF8.GetString(fileData, startIndex, i - startIndex + 1); - - // Update startindex for next json search - startIndex = i; - - return JsonSerializer.Deserialize(json, _options); + ReadOnlySpan jsonSpan = fileData.Slice(startIndex, i - startIndex + 1); + return JsonSerializer.Deserialize(jsonSpan, _options); } } } - catch (Exception) + catch { return default; } @@ -211,27 +222,16 @@ private static bool IsTempFile(string fileName) return fileName.Contains("temp"); } - private void AddBattleReport(string name, BattleReport report) + private void AddBattleReport(string name, BattleReport report, bool notify = true) { if (!BattleReports.TryAdd(name, report)) { _snackbar.Add($"Kan bestand '{name}' niet toevoegen", Severity.Error); } - BattleReportAdded?.Invoke(this, new BattleReportAddedEventArgs(report)); - } - - private static int FindSequenceIndex(byte[] byteArray, byte[] sequence) - { - for (int i = 0; i <= byteArray.Length - sequence.Length; i++) + if (notify) { - if (byteArray.Skip(i).Take(sequence.Length).SequenceEqual(sequence)) - { - return i; - } + BattleReportAdded?.Invoke(this, new BattleReportAddedEventArgs(report)); } - return 0; } - -} - +} \ No newline at end of file diff --git a/NED.WoT.BattleResults.Client/Services/MapNameResolver.cs b/NED.WoT.BattleResults.Client/Services/MapNameResolver.cs index b32f81d..97f3afa 100644 --- a/NED.WoT.BattleResults.Client/Services/MapNameResolver.cs +++ b/NED.WoT.BattleResults.Client/Services/MapNameResolver.cs @@ -1,94 +1,103 @@ -namespace NED.WoT.BattleResults.Client.Services +namespace NED.WoT.BattleResults.Client.Services; + +public static class MapNameResolver { - public static class MapNameResolver + public static HashSet UndefinedMapNames = []; + + private static readonly Dictionary _mapNames = new() + { + {"Abdij","Abbey"}, + {"Vliegveld","Airfield"}, + {"Klif","Cliff"}, + {"Rijksgrens","Empire's Border"}, + {"Vissersbaai","Fisherman's Bay"}, + {"Fjorden","Fjords"}, + {"Spookstad","Ghost Town"}, + {"Gletsjer","Glacier"}, + {"Snelweg","Highway"}, + {"Karelië","Karelia"}, + {"Mannerheim Linie","Mannerheim Line"}, + {"Mijngebied","Mines"}, + {"Bergpas","Mountain Pass"}, + {"Buitenpost","Outpost"}, + {"Oesterbaai","Oyster Bay"}, + {"Parelrivier","Pearl River"}, + {"Parijs","Paris"}, + {"Provincie","Province"}, + {"Veilige haven","Safe Haven"}, + {"Zandrivier","Sand River"}, + {"Serene kust","Serene Coast"}, + {"Siegfriedlinie","Siegfried Line"}, + {"Steppe","Steppes"}, + {"Toendra","Tundra"}, + {"Stadspark","Widepark"}, + + {"Karelia", "Karelia"}, + {"Lakeville", "Lakeville"}, + {"Ghost Town", "Ghost Town"}, + {"Sand River", "Sand River"}, + {"El Halluf", "El Halluf"}, + {"Malinovka", "Malinovka"}, + {"Himmelsdorf", "Himmelsdorf"}, + {"Paris", "Paris"}, + {"Siegfried Line", "Siegfried Line"}, + {"Ruinberg", "Ruinberg"}, + {"Steppes", "Steppes"}, + {"Prokhorovka", "Prokhorovka"}, + {"Province", "Province"}, + {"Mines", "Mines"}, + {"Outpost", "Outpost"}, + {"Cliff", "Cliff"}, + {"Abbey", "Abbey"}, + {"Pearl River", "Pearl River"}, + {"Berlin", "Berlin"}, + {"Pilsen", "Pilsen"}, + {"Tundra", "Tundra"}, + {"Live Oaks", "Live Oaks"}, + {"Mannerheim Line", "Mannerheim Line"}, + {"Fisherman's Bay", "Fisherman's Bay"}, + {"Mountain Pass", "Mountain Pass"}, + {"Widepark", "Widepark"}, + {"Glacier", "Glacier"}, + {"Murovanka", "Murovanka"}, + {"Erlenberg", "Erlenberg"}, + {"Redshire", "Redshire"}, + {"Westfield", "Westfield"}, + {"Overlord", "Overlord"}, + {"Fjords", "Fjords"}, + {"Ensk", "Ensk"}, + {"Airfield", "Airfield"}, + {"Highway", "Highway"}, + {"Safe Haven", "Safe Haven"}, + {"Empire's Border", "Empire's Border"} + }; + + public static string GetMapName(string key) { - public static HashSet UndefinedMapNames = []; + if (string.IsNullOrEmpty(key)) + { + return key; + } - private static readonly Dictionary _mapNames = new() + if (_mapNames.TryGetValue(key, out string name)) { - {"Abdij","Abbey"}, - {"Vliegveld","Airfield"}, - {"Klif","Cliff"}, - {"Rijksgrens","Empire's Border"}, - {"Vissersbaai","Fisherman's Bay"}, - {"Fjorden","Fjords"}, - {"Spookstad","Ghost Town"}, - {"Gletsjer","Glacier"}, - {"Snelweg","Highway"}, - {"Karelië","Karelia"}, - {"Mannerheim Linie","Mannerheim Line"}, - {"Mijngebied","Mines"}, - {"Bergpas","Mountain Pass"}, - {"Buitenpost","Outpost"}, - {"Oesterbaai","Oyster Bay"}, - {"Parelrivier","Pearl River"}, - {"Parijs","Paris"}, - {"Provincie","Province"}, - {"Veilige haven","Safe Haven"}, - {"Zandrivier","Sand River"}, - {"Serene kust","Serene Coast"}, - {"Siegfriedlinie","Siegfried Line"}, - {"Steppe","Steppes"}, - {"Toendra","Tundra"}, - {"Stadspark","Widepark"}, + return name; + } - {"Karelia", "Karelia"}, - {"Lakeville", "Lakeville"}, - {"Ghost Town", "Ghost Town"}, - {"Sand River", "Sand River"}, - {"El Halluf", "El Halluf"}, - {"Malinovka", "Malinovka"}, - {"Himmelsdorf", "Himmelsdorf"}, - {"Paris", "Paris"}, - {"Siegfried Line", "Siegfried Line"}, - {"Ruinberg", "Ruinberg"}, - {"Steppes", "Steppes"}, - {"Prokhorovka", "Prokhorovka"}, - {"Province", "Province"}, - {"Mines", "Mines"}, - {"Outpost", "Outpost"}, - {"Cliff", "Cliff"}, - {"Abbey", "Abbey"}, - {"Pearl River", "Pearl River"}, - {"Berlin", "Berlin"}, - {"Pilsen", "Pilsen"}, - {"Tundra", "Tundra"}, - {"Live Oaks", "Live Oaks"}, - {"Mannerheim Line", "Mannerheim Line"}, - {"Fisherman's Bay", "Fisherman's Bay"}, - {"Mountain Pass", "Mountain Pass"}, - {"Widepark", "Widepark"}, - {"Glacier", "Glacier"}, - {"Murovanka", "Murovanka"}, - {"Erlenberg", "Erlenberg"}, - {"Redshire", "Redshire"}, - {"Westfield", "Westfield"}, - {"Overlord", "Overlord"}, - {"Fjords", "Fjords"}, - {"Ensk", "Ensk"}, - {"Airfield", "Airfield"}, - {"Highway", "Highway"}, - {"Safe Haven", "Safe Haven"}, - {"Empire's Border", "Empire's Border"} - }; + ReadOnlySpan keySpan = key.AsSpan(); - public static string GetMapName(string key) + int semiColonIndex = keySpan.IndexOf(':'); + if (semiColonIndex != -1) { - string strippedKey = key[(key.IndexOf(':') + 1)..]; - if (_mapNames.TryGetValue(strippedKey, out var name)) + ReadOnlySpan strippedKey = keySpan[(semiColonIndex + 1)..]; + if (_mapNames.TryGetValue(strippedKey.ToString(), out name)) { return name; } + } - if (!string.IsNullOrWhiteSpace(key)) - { - UndefinedMapNames.Add(key); - } + UndefinedMapNames.Add(key); - return key; - } + return key; } -} - - - +} \ No newline at end of file diff --git a/NED.WoT.BattleResults.Client/Services/SettingService.cs b/NED.WoT.BattleResults.Client/Services/SettingService.cs index 87b1706..92eef42 100644 --- a/NED.WoT.BattleResults.Client/Services/SettingService.cs +++ b/NED.WoT.BattleResults.Client/Services/SettingService.cs @@ -16,6 +16,7 @@ public SettingService() PlayerName = Preferences.Default.Get(nameof(Settings.PlayerName), string.Empty), SingleBattleResultOpenedOnly = Preferences.Default.Get(nameof(Settings.SingleBattleResultOpenedOnly), false), OnlyHighlistOwnMatches = Preferences.Default.Get(nameof(Settings.OnlyHighlistOwnMatches), false), + StartupUpdateOnEveryReport = Preferences.Default.Get(nameof(Settings.StartupUpdateOnEveryReport), false), }; } public void Save(Settings settings) @@ -28,6 +29,7 @@ public void Save(Settings settings) Preferences.Default.Set(nameof(Settings.PlayerName), Settings.PlayerName); Preferences.Default.Set(nameof(Settings.SingleBattleResultOpenedOnly), Settings.SingleBattleResultOpenedOnly); Preferences.Default.Set(nameof(Settings.OnlyHighlistOwnMatches), Settings.OnlyHighlistOwnMatches); + Preferences.Default.Set(nameof(Settings.StartupUpdateOnEveryReport), Settings.StartupUpdateOnEveryReport); } } } diff --git a/NED.WoT.BattleResults.Client/Services/TankNameResolver.cs b/NED.WoT.BattleResults.Client/Services/TankNameResolver.cs index 8536c31..042f88f 100644 --- a/NED.WoT.BattleResults.Client/Services/TankNameResolver.cs +++ b/NED.WoT.BattleResults.Client/Services/TankNameResolver.cs @@ -1465,29 +1465,41 @@ public static class TankNameResolver public static string GetTankName(string key) { - if (string.IsNullOrWhiteSpace(key)) + if (string.IsNullOrEmpty(key)) { return key; } - string strippedKey = key[(key.IndexOf(':') + 1)..]; - if (_tankNames.TryGetValue(strippedKey, out var name)) + if (_tankNames.TryGetValue(key, out string name)) { return name; } - strippedKey = strippedKey[..strippedKey.LastIndexOf('_')]; - if (_tankNames.TryGetValue(strippedKey, out name)) + ReadOnlySpan keySpan = key.AsSpan(); + + int semiColonIndex = keySpan.IndexOf(':'); + if (semiColonIndex != -1) { - return name; + keySpan = keySpan[(semiColonIndex + 1)..]; + if (_tankNames.TryGetValue(keySpan.ToString(), out name)) + { + return name; + } + } + + int underscoreIndex = keySpan.LastIndexOf('_'); + if (underscoreIndex != -1) + { + keySpan = keySpan[..underscoreIndex]; + if (_tankNames.TryGetValue(keySpan.ToString(), out name)) + { + return name; + } } - - UndefinedTankNames.Add(key); + + UndefinedTankNames.Add(key); return key; } } -} - - - +} \ No newline at end of file