diff --git a/src/Entities/CPUInfo.cs b/src/Entities/CPUInfo.cs index 9d06ded..24d5423 100644 --- a/src/Entities/CPUInfo.cs +++ b/src/Entities/CPUInfo.cs @@ -4,33 +4,33 @@ namespace PrimeView.Entities { - public class CPUInfo - { - public string? Manufacturer { get; set; } - public string? Brand { get; set; } - public string? Vendor { get; set; } - public string? Family { get; set; } - public string? Model { get; set; } - public string? Stepping { get; set; } - public string? Revision { get; set; } - public string? Voltage { get; set; } - public float? Speed { get; set; } - [JsonPropertyName("speedMin")] - public float? MinimumSpeed { get; set; } - [JsonPropertyName("speedMax")] - public float? MaximumSpeed { get; set; } - public string? Governor { get; set; } - public int? Cores { get; set; } - public int? PhysicalCores { get; set; } - public int? EfficiencyCores { get; set; } - public int? PerformanceCores { get; set; } - public int? Processors { get; set; } - public string? RaspberryProcessor { get; set; } - public string? Socket { get; set; } - public string? Flags { get; set; } - [JsonIgnore] - public string[]? FlagValues => Flags?.Split(' ', StringSplitOptions.RemoveEmptyEntries); - public bool? Virtualization { get; set; } - public Dictionary? Cache { get; set; } - } + public class CPUInfo + { + public string? Manufacturer { get; set; } + public string? Brand { get; set; } + public string? Vendor { get; set; } + public string? Family { get; set; } + public string? Model { get; set; } + public string? Stepping { get; set; } + public string? Revision { get; set; } + public string? Voltage { get; set; } + public float? Speed { get; set; } + [JsonPropertyName("speedMin")] + public float? MinimumSpeed { get; set; } + [JsonPropertyName("speedMax")] + public float? MaximumSpeed { get; set; } + public string? Governor { get; set; } + public int? Cores { get; set; } + public int? PhysicalCores { get; set; } + public int? EfficiencyCores { get; set; } + public int? PerformanceCores { get; set; } + public int? Processors { get; set; } + public string? RaspberryProcessor { get; set; } + public string? Socket { get; set; } + public string? Flags { get; set; } + [JsonIgnore] + public string[]? FlagValues => Flags?.Split(' ', StringSplitOptions.RemoveEmptyEntries); + public bool? Virtualization { get; set; } + public Dictionary? Cache { get; set; } + } } diff --git a/src/Entities/DockerInfo.cs b/src/Entities/DockerInfo.cs index 6bbd501..6bbd2f1 100644 --- a/src/Entities/DockerInfo.cs +++ b/src/Entities/DockerInfo.cs @@ -2,17 +2,17 @@ namespace PrimeView.Entities { - public class DockerInfo - { - public string? KernelVersion { get; set; } - public string? OperatingSystem { get; set; } - public string? OSVersion { get; set; } - public string? OSType { get; set; } - public string? Architecture { get; set; } - [JsonPropertyName("ncpu")] - public int? CPUCount { get; set; } - [JsonPropertyName("memTotal")] - public long? TotalMemory { get; set; } - public string? ServerVersion { get; set; } - } + public class DockerInfo + { + public string? KernelVersion { get; set; } + public string? OperatingSystem { get; set; } + public string? OSVersion { get; set; } + public string? OSType { get; set; } + public string? Architecture { get; set; } + [JsonPropertyName("ncpu")] + public int? CPUCount { get; set; } + [JsonPropertyName("memTotal")] + public long? TotalMemory { get; set; } + public string? ServerVersion { get; set; } + } } diff --git a/src/Entities/IReportReader.cs b/src/Entities/IReportReader.cs index 9e5458e..e4922b8 100644 --- a/src/Entities/IReportReader.cs +++ b/src/Entities/IReportReader.cs @@ -2,12 +2,12 @@ namespace PrimeView.Entities { - public interface IReportReader - { - Task<(ReportSummary[] summaries, int total)> GetSummaries(int maxSummaryCount); - Task<(ReportSummary[] summaries, int total)> GetSummaries(string? runnerId, int skipFirst, int maxSummaryCount); - Task GetReport(string id); - Task GetRunners(); - void FlushCache(); - } + public interface IReportReader + { + Task<(ReportSummary[] summaries, int total)> GetSummaries(int maxSummaryCount); + Task<(ReportSummary[] summaries, int total)> GetSummaries(string? runnerId, int skipFirst, int maxSummaryCount); + Task GetReport(string id); + Task GetRunners(); + void FlushCache(); + } } diff --git a/src/Entities/OperatingSystemInfo.cs b/src/Entities/OperatingSystemInfo.cs index 1f3f257..ca095a6 100644 --- a/src/Entities/OperatingSystemInfo.cs +++ b/src/Entities/OperatingSystemInfo.cs @@ -2,21 +2,21 @@ namespace PrimeView.Entities { - public class OperatingSystemInfo - { - public string? Platform { get; set; } - [JsonPropertyName("distro")] - public string? Distribution { get; set; } - public string? Release { get; set; } - public string? CodeName { get; set; } - public string? Kernel { get; set; } - [JsonPropertyName("arch")] - public string? Architecture { get; set; } - public string? CodePage { get; set; } - public string? LogoFile { get; set; } - public string? Build { get; set; } - public string? ServicePack { get; set; } - [JsonPropertyName("uefi")] - public bool? IsUefi { get; set; } - } + public class OperatingSystemInfo + { + public string? Platform { get; set; } + [JsonPropertyName("distro")] + public string? Distribution { get; set; } + public string? Release { get; set; } + public string? CodeName { get; set; } + public string? Kernel { get; set; } + [JsonPropertyName("arch")] + public string? Architecture { get; set; } + public string? CodePage { get; set; } + public string? LogoFile { get; set; } + public string? Build { get; set; } + public string? ServicePack { get; set; } + [JsonPropertyName("uefi")] + public bool? IsUefi { get; set; } + } } diff --git a/src/Entities/Report.cs b/src/Entities/Report.cs index 34e1c18..421fd87 100644 --- a/src/Entities/Report.cs +++ b/src/Entities/Report.cs @@ -2,15 +2,15 @@ namespace PrimeView.Entities { - public class Report - { - public string? Id { get; set; } - public string? User { get; set; } - public DateTime? Date { get; set; } - public CPUInfo? CPU { get; set; } - public OperatingSystemInfo? OperatingSystem { get; set; } - public SystemInfo? System { get; set; } - public DockerInfo? DockerInfo { get; set; } - public Result[]? Results { get; set; } - } + public class Report + { + public string? Id { get; set; } + public string? User { get; set; } + public DateTime? Date { get; set; } + public CPUInfo? CPU { get; set; } + public OperatingSystemInfo? OperatingSystem { get; set; } + public SystemInfo? System { get; set; } + public DockerInfo? DockerInfo { get; set; } + public Result[]? Results { get; set; } + } } diff --git a/src/Entities/ReportSummary.cs b/src/Entities/ReportSummary.cs index 6ce46d6..08e2abd 100644 --- a/src/Entities/ReportSummary.cs +++ b/src/Entities/ReportSummary.cs @@ -2,21 +2,21 @@ namespace PrimeView.Entities { - public class ReportSummary - { - public string? Id { get; set; } - public DateTime? Date { get; set; } - public string? User { get; set; } - public string? CpuVendor { get; set; } - public string? CpuBrand { get; set; } - public int? CpuCores { get; set; } - public int? CpuProcessors { get; set; } - public string? OsPlatform { get; set; } - public string? OsDistro { get; set; } - public string? OsRelease { get; set; } - public string? Architecture { get; set; } - public bool? IsSystemVirtual { get; set; } - public string? DockerArchitecture { get; set; } - public int ResultCount { get; set; } = 0; - } + public class ReportSummary + { + public string? Id { get; set; } + public DateTime? Date { get; set; } + public string? User { get; set; } + public string? CpuVendor { get; set; } + public string? CpuBrand { get; set; } + public int? CpuCores { get; set; } + public int? CpuProcessors { get; set; } + public string? OsPlatform { get; set; } + public string? OsDistro { get; set; } + public string? OsRelease { get; set; } + public string? Architecture { get; set; } + public bool? IsSystemVirtual { get; set; } + public string? DockerArchitecture { get; set; } + public int ResultCount { get; set; } = 0; + } } diff --git a/src/Entities/Result.cs b/src/Entities/Result.cs index 9d78d34..3bb300b 100644 --- a/src/Entities/Result.cs +++ b/src/Entities/Result.cs @@ -3,58 +3,58 @@ namespace PrimeView.Entities { - [EpplusTable(PrintHeaders = true, ShowTotal = true, TableStyle = TableStyles.Light1)] - public class Result - { - public const int LanguageColumnIndex = 1; - [EpplusTableColumn(Order = LanguageColumnIndex, TotalsRowLabel = "Count:")] - public string? Language { get; set; } - - public const int SolutionColumnIndex = 2; - [EpplusTableColumn(Order = SolutionColumnIndex, TotalsRowFunction = RowFunctions.Count)] - public string? Solution { get; set; } - - public const int SolutionUriColumnIndex = 3; - [EpplusTableColumn(Order = SolutionUriColumnIndex, Header = "Solution link")] - public string? SolutionUrl { get; set; } - - public const int LabelColumnIndex = 4; - [EpplusTableColumn(Order = LabelColumnIndex)] - public string? Label { get; set; } - - public const int IsMultiThreadedColumnIndex = 5; - [EpplusTableColumn(Order = IsMultiThreadedColumnIndex, Header = "Multithreaded?")] - public bool IsMultiThreaded => Threads > 1; - - public const int PassesColumnIndex = 6; - [EpplusTableColumn(Order = PassesColumnIndex, Header = "Number of passes")] - public long? Passes { get; set; } - - public const int DurationColumnIndex = 7; - [EpplusTableColumn(Order = DurationColumnIndex)] - public double? Duration { get; set; } - - public const int ThreadsColumnIndex = 8; - [EpplusTableColumn(Order = ThreadsColumnIndex, Header = "Number of threads")] - public int? Threads { get; set; } - - public const int PassesPerSecondColumnIndex = 9; - [EpplusTableColumn(Order = PassesPerSecondColumnIndex, Header = "Passes / thread / second")] - public double? PassesPerSecond => (double?)Passes / Threads / Duration; - - public const int AlgorithmColumnIndex = 10; - [EpplusTableColumn(Order = AlgorithmColumnIndex)] - public string? Algorithm { get; set; } - - public const int IsFaithfulColumnIndex = 11; - [EpplusTableColumn(Order = IsFaithfulColumnIndex, Header = "Faithful?")] - public bool? IsFaithful { get; set; } - - public const int BitsColumnIndex = 12; - [EpplusTableColumn(Order = BitsColumnIndex, Header = "Bits per prime")] - public int? Bits { get; set; } - - [EpplusIgnore] - public string? Status { get; set; } - } + [EpplusTable(PrintHeaders = true, ShowTotal = true, TableStyle = TableStyles.Light1)] + public class Result + { + public const int LanguageColumnIndex = 1; + [EpplusTableColumn(Order = LanguageColumnIndex, TotalsRowLabel = "Count:")] + public string? Language { get; set; } + + public const int SolutionColumnIndex = 2; + [EpplusTableColumn(Order = SolutionColumnIndex, TotalsRowFunction = RowFunctions.Count)] + public string? Solution { get; set; } + + public const int SolutionUriColumnIndex = 3; + [EpplusTableColumn(Order = SolutionUriColumnIndex, Header = "Solution link")] + public string? SolutionUrl { get; set; } + + public const int LabelColumnIndex = 4; + [EpplusTableColumn(Order = LabelColumnIndex)] + public string? Label { get; set; } + + public const int IsMultiThreadedColumnIndex = 5; + [EpplusTableColumn(Order = IsMultiThreadedColumnIndex, Header = "Multithreaded?")] + public bool IsMultiThreaded => Threads > 1; + + public const int PassesColumnIndex = 6; + [EpplusTableColumn(Order = PassesColumnIndex, Header = "Number of passes")] + public long? Passes { get; set; } + + public const int DurationColumnIndex = 7; + [EpplusTableColumn(Order = DurationColumnIndex)] + public double? Duration { get; set; } + + public const int ThreadsColumnIndex = 8; + [EpplusTableColumn(Order = ThreadsColumnIndex, Header = "Number of threads")] + public int? Threads { get; set; } + + public const int PassesPerSecondColumnIndex = 9; + [EpplusTableColumn(Order = PassesPerSecondColumnIndex, Header = "Passes / thread / second")] + public double? PassesPerSecond => (double?)Passes / Threads / Duration; + + public const int AlgorithmColumnIndex = 10; + [EpplusTableColumn(Order = AlgorithmColumnIndex)] + public string? Algorithm { get; set; } + + public const int IsFaithfulColumnIndex = 11; + [EpplusTableColumn(Order = IsFaithfulColumnIndex, Header = "Faithful?")] + public bool? IsFaithful { get; set; } + + public const int BitsColumnIndex = 12; + [EpplusTableColumn(Order = BitsColumnIndex, Header = "Bits per prime")] + public int? Bits { get; set; } + + [EpplusIgnore] + public string? Status { get; set; } + } } diff --git a/src/Entities/Runner.cs b/src/Entities/Runner.cs index dd68278..498a126 100644 --- a/src/Entities/Runner.cs +++ b/src/Entities/Runner.cs @@ -1,8 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Text; namespace PrimeView.Entities { @@ -32,7 +28,7 @@ public string Description if (CPU?.Brand != null) builder.Append($"{CPU.Brand} "); - + if (CPU?.Cores != null) builder.Append($"({CPU.Cores} cores) "); diff --git a/src/Entities/SystemInfo.cs b/src/Entities/SystemInfo.cs index f901aa4..fd5f86a 100644 --- a/src/Entities/SystemInfo.cs +++ b/src/Entities/SystemInfo.cs @@ -2,16 +2,16 @@ namespace PrimeView.Entities { - public class SystemInfo - { - public string? Manufacturer { get; set; } - public string? Model { get; set; } - public string? Version { get; set; } - public string? SKU { get; set; } - public string? RaspberryManufacturer { get; set; } - public string? RaspberryType { get; set; } - public string? RaspberryRevision { get; set; } - [JsonPropertyName("virtual")] - public bool? IsVirtual { get; set; } - } + public class SystemInfo + { + public string? Manufacturer { get; set; } + public string? Model { get; set; } + public string? Version { get; set; } + public string? SKU { get; set; } + public string? RaspberryManufacturer { get; set; } + public string? RaspberryType { get; set; } + public string? RaspberryRevision { get; set; } + [JsonPropertyName("virtual")] + public bool? IsVirtual { get; set; } + } } diff --git a/src/Frontend/Filters/FilterExtensions.cs b/src/Frontend/Filters/FilterExtensions.cs index 04d8d11..d5ce9da 100644 --- a/src/Frontend/Filters/FilterExtensions.cs +++ b/src/Frontend/Filters/FilterExtensions.cs @@ -8,64 +8,64 @@ namespace PrimeView.Frontend.Filters { - public static class FilterExtensions - { - private static readonly Func successfulResult; + public static class FilterExtensions + { + private static readonly Func successfulResult; - static FilterExtensions() - { - Expression> successfulResultExpression = result => result.Status == null || result.Status == "success"; - successfulResult = successfulResultExpression.Compile(); - } + static FilterExtensions() + { + Expression> successfulResultExpression = result => result.Status == null || result.Status == "success"; + successfulResult = successfulResultExpression.Compile(); + } - public static IEnumerable Viewable(this IEnumerable source) - { - return source.Where(successfulResult); - } + public static IEnumerable Viewable(this IEnumerable source) + { + return source.Where(successfulResult); + } - public static IEnumerable ApplyFilters(this IEnumerable source, ReportDetails page) - { - var filterLanguages = page.FilterLanguages; + public static IEnumerable ApplyFilters(this IEnumerable source, ReportDetails page) + { + var filterLanguages = page.FilterLanguages; - if (filterLanguages.Count > 0) - source = source.Where(r => filterLanguages.Contains(r.Language)); + if (filterLanguages.Count > 0) + source = source.Where(r => filterLanguages.Contains(r.Language)); - var filteredResults = source.Where(r => - r.IsMultiThreaded switch - { - true => page.FilterParallelMultithreaded, - _ => page.FilterParallelSinglethreaded - } - && r.Algorithm switch - { - "base" => page.FilterAlgorithmBase, - "wheel" => page.FilterAlgorithmWheel, - _ => page.FilterAlgorithmOther - } - && r.IsFaithful switch - { - true => page.FilterFaithful, - _ => page.FilterUnfaithful - } - && r.Bits switch - { - null => page.FilterBitsUnknown, - 1 => page.FilterBitsOne, - _ => page.FilterBitsOther - } - ); + var filteredResults = source.Where(r => + r.IsMultiThreaded switch + { + true => page.FilterParallelMultithreaded, + _ => page.FilterParallelSinglethreaded + } + && r.Algorithm switch + { + "base" => page.FilterAlgorithmBase, + "wheel" => page.FilterAlgorithmWheel, + _ => page.FilterAlgorithmOther + } + && r.IsFaithful switch + { + true => page.FilterFaithful, + _ => page.FilterUnfaithful + } + && r.Bits switch + { + null => page.FilterBitsUnknown, + 1 => page.FilterBitsOne, + _ => page.FilterBitsOther + } + ); - return page.OnlyHighestPassesPerSecondPerThreadPerLanguage - ? filteredResults - .GroupBy(r => r.Language) - .SelectMany(group => group.Where(r => r.PassesPerSecond == group.Max(r => r.PassesPerSecond))) - : filteredResults; - } + return page.OnlyHighestPassesPerSecondPerThreadPerLanguage + ? filteredResults + .GroupBy(r => r.Language) + .SelectMany(group => group.Where(r => r.PassesPerSecond == group.Max(r => r.PassesPerSecond))) + : filteredResults; + } - public static string CreateSummary(this IResultFilterPropertyProvider filter, ILanguageInfoProvider languageInfoProvider) - { - List segments = new() - { + public static string CreateSummary(this IResultFilterPropertyProvider filter, ILanguageInfoProvider languageInfoProvider) + { + List segments = + [ filter.FilterLanguages.Count switch { 0 => "all languages", @@ -107,19 +107,19 @@ public static string CreateSummary(this IResultFilterPropertyProvider filter, IL (true, false, true) => "one or unknown bits", _ => null } - }; + ]; return string.Join(", ", segments.Where(s => s != null)); - } + } - public static IList SplitFilterValues(this string text) - { - return text.Split('~', StringSplitOptions.RemoveEmptyEntries); - } + public static IList SplitFilterValues(this string text) + { + return text.Split('~', StringSplitOptions.RemoveEmptyEntries); + } - public static string JoinFilterValues(this IEnumerable values) - { - return string.Join('~', values); - } - } + public static string JoinFilterValues(this IEnumerable values) + { + return string.Join('~', values); + } + } } diff --git a/src/Frontend/Filters/IResultFilterPropertyProvider.cs b/src/Frontend/Filters/IResultFilterPropertyProvider.cs index 0e5f71e..8c24d3d 100644 --- a/src/Frontend/Filters/IResultFilterPropertyProvider.cs +++ b/src/Frontend/Filters/IResultFilterPropertyProvider.cs @@ -2,22 +2,22 @@ namespace PrimeView.Frontend.Filters { - public interface IResultFilterPropertyProvider - { - public IList FilterLanguages { get; } + public interface IResultFilterPropertyProvider + { + public IList FilterLanguages { get; } - public bool FilterParallelSinglethreaded { get; } - public bool FilterParallelMultithreaded { get; } + public bool FilterParallelSinglethreaded { get; } + public bool FilterParallelMultithreaded { get; } - public bool FilterAlgorithmBase { get; } - public bool FilterAlgorithmWheel { get; } - public bool FilterAlgorithmOther { get; } + public bool FilterAlgorithmBase { get; } + public bool FilterAlgorithmWheel { get; } + public bool FilterAlgorithmOther { get; } - public bool FilterFaithful { get; } - public bool FilterUnfaithful { get; } + public bool FilterFaithful { get; } + public bool FilterUnfaithful { get; } - public bool FilterBitsUnknown { get; } - public bool FilterBitsOne { get; } - public bool FilterBitsOther { get; } - } + public bool FilterBitsUnknown { get; } + public bool FilterBitsOne { get; } + public bool FilterBitsOther { get; } + } } diff --git a/src/Frontend/Filters/LeaderboardFilterPreset.cs b/src/Frontend/Filters/LeaderboardFilterPreset.cs index 3896582..3b0614f 100644 --- a/src/Frontend/Filters/LeaderboardFilterPreset.cs +++ b/src/Frontend/Filters/LeaderboardFilterPreset.cs @@ -2,18 +2,18 @@ namespace PrimeView.Frontend.Filters { - public class LeaderboardFilterPreset : ResultFilterPreset - { - public LeaderboardFilterPreset() - { - Name = "Leaderboard"; - ImplementationText = string.Empty; - ParallelismText = Constants.MultithreadedTag; - AlgorithmText = new string[] { Constants.WheelTag, Constants.OtherTag }.JoinFilterValues(); - FaithfulText = Constants.UnfaithfulTag; - BitsText = new string[] { Constants.UnknownTag, Constants.OtherTag }.JoinFilterValues(); - } + public class LeaderboardFilterPreset : ResultFilterPreset + { + public LeaderboardFilterPreset() + { + Name = "Leaderboard"; + ImplementationText = string.Empty; + ParallelismText = Constants.MultithreadedTag; + AlgorithmText = new string[] { Constants.WheelTag, Constants.OtherTag }.JoinFilterValues(); + FaithfulText = Constants.UnfaithfulTag; + BitsText = new string[] { Constants.UnknownTag, Constants.OtherTag }.JoinFilterValues(); + } - public override bool IsFixed => true; - } + public override bool IsFixed => true; + } } diff --git a/src/Frontend/Filters/MultithreadedLeaderboardFilterPreset.cs b/src/Frontend/Filters/MultithreadedLeaderboardFilterPreset.cs index 82cae73..e556563 100644 --- a/src/Frontend/Filters/MultithreadedLeaderboardFilterPreset.cs +++ b/src/Frontend/Filters/MultithreadedLeaderboardFilterPreset.cs @@ -2,18 +2,18 @@ namespace PrimeView.Frontend.Filters { - public class MultithreadedLeaderboardFilterPreset : ResultFilterPreset - { - public MultithreadedLeaderboardFilterPreset() - { - Name = "Multithreaded leaderboard"; - ImplementationText = string.Empty; - ParallelismText = Constants.SinglethreadedTag; - AlgorithmText = new string[] { Constants.WheelTag, Constants.OtherTag }.JoinFilterValues(); - FaithfulText = Constants.UnfaithfulTag; - BitsText = new string[] { Constants.UnknownTag, Constants.OtherTag }.JoinFilterValues(); - } + public class MultithreadedLeaderboardFilterPreset : ResultFilterPreset + { + public MultithreadedLeaderboardFilterPreset() + { + Name = "Multithreaded leaderboard"; + ImplementationText = string.Empty; + ParallelismText = Constants.SinglethreadedTag; + AlgorithmText = new string[] { Constants.WheelTag, Constants.OtherTag }.JoinFilterValues(); + FaithfulText = Constants.UnfaithfulTag; + BitsText = new string[] { Constants.UnknownTag, Constants.OtherTag }.JoinFilterValues(); + } - public override bool IsFixed => true; - } + public override bool IsFixed => true; + } } diff --git a/src/Frontend/Filters/ResultFilterPreset.cs b/src/Frontend/Filters/ResultFilterPreset.cs index eb9cc5a..636f449 100644 --- a/src/Frontend/Filters/ResultFilterPreset.cs +++ b/src/Frontend/Filters/ResultFilterPreset.cs @@ -4,71 +4,71 @@ namespace PrimeView.Frontend.Filters { - public class ResultFilterPreset : IResultFilterPropertyProvider - { - [JsonPropertyName("nm")] - public string Name { get; set; } + public class ResultFilterPreset : IResultFilterPropertyProvider + { + [JsonPropertyName("nm")] + public string Name { get; set; } - [JsonPropertyName("it")] - public string ImplementationText { get; set; } + [JsonPropertyName("it")] + public string ImplementationText { get; set; } - [JsonPropertyName("pt")] - public string ParallelismText { get; set; } + [JsonPropertyName("pt")] + public string ParallelismText { get; set; } - [JsonPropertyName("at")] - public string AlgorithmText { get; set; } + [JsonPropertyName("at")] + public string AlgorithmText { get; set; } - [JsonPropertyName("ft")] - public string FaithfulText { get; set; } + [JsonPropertyName("ft")] + public string FaithfulText { get; set; } - [JsonPropertyName("bt")] - public string BitsText { get; set; } + [JsonPropertyName("bt")] + public string BitsText { get; set; } - [JsonIgnore] - public virtual bool IsFixed => false; + [JsonIgnore] + public virtual bool IsFixed => false; - [JsonIgnore] - public IList FilterLanguages - => ImplementationText.SplitFilterValues(); + [JsonIgnore] + public IList FilterLanguages + => ImplementationText.SplitFilterValues(); - [JsonIgnore] - public bool FilterParallelSinglethreaded - => !ParallelismText.SplitFilterValues().Contains(Constants.SinglethreadedTag); + [JsonIgnore] + public bool FilterParallelSinglethreaded + => !ParallelismText.SplitFilterValues().Contains(Constants.SinglethreadedTag); - [JsonIgnore] - public bool FilterParallelMultithreaded - => !ParallelismText.SplitFilterValues().Contains(Constants.MultithreadedTag); + [JsonIgnore] + public bool FilterParallelMultithreaded + => !ParallelismText.SplitFilterValues().Contains(Constants.MultithreadedTag); - [JsonIgnore] - public bool FilterAlgorithmBase - => !AlgorithmText.SplitFilterValues().Contains(Constants.BaseTag); + [JsonIgnore] + public bool FilterAlgorithmBase + => !AlgorithmText.SplitFilterValues().Contains(Constants.BaseTag); - [JsonIgnore] - public bool FilterAlgorithmWheel - => !AlgorithmText.SplitFilterValues().Contains(Constants.WheelTag); + [JsonIgnore] + public bool FilterAlgorithmWheel + => !AlgorithmText.SplitFilterValues().Contains(Constants.WheelTag); - [JsonIgnore] - public bool FilterAlgorithmOther - => !AlgorithmText.SplitFilterValues().Contains(Constants.OtherTag); + [JsonIgnore] + public bool FilterAlgorithmOther + => !AlgorithmText.SplitFilterValues().Contains(Constants.OtherTag); - [JsonIgnore] - public bool FilterFaithful - => !FaithfulText.SplitFilterValues().Contains(Constants.FaithfulTag); + [JsonIgnore] + public bool FilterFaithful + => !FaithfulText.SplitFilterValues().Contains(Constants.FaithfulTag); - [JsonIgnore] - public bool FilterUnfaithful - => !FaithfulText.SplitFilterValues().Contains(Constants.UnfaithfulTag); + [JsonIgnore] + public bool FilterUnfaithful + => !FaithfulText.SplitFilterValues().Contains(Constants.UnfaithfulTag); - [JsonIgnore] - public bool FilterBitsUnknown - => !BitsText.SplitFilterValues().Contains(Constants.UnknownTag); + [JsonIgnore] + public bool FilterBitsUnknown + => !BitsText.SplitFilterValues().Contains(Constants.UnknownTag); - [JsonIgnore] - public bool FilterBitsOne - => !BitsText.SplitFilterValues().Contains(Constants.OneTag); + [JsonIgnore] + public bool FilterBitsOne + => !BitsText.SplitFilterValues().Contains(Constants.OneTag); - [JsonIgnore] - public bool FilterBitsOther - => !BitsText.SplitFilterValues().Contains(Constants.OtherTag); - } + [JsonIgnore] + public bool FilterBitsOther + => !BitsText.SplitFilterValues().Contains(Constants.OtherTag); + } } diff --git a/src/Frontend/Pages/Index.razor.cs b/src/Frontend/Pages/Index.razor.cs index fed1d4a..8081af4 100644 --- a/src/Frontend/Pages/Index.razor.cs +++ b/src/Frontend/Pages/Index.razor.cs @@ -7,138 +7,139 @@ namespace PrimeView.Frontend.Pages { - public partial class Index - { - [Inject] - public IConfiguration Configuration { get; set; } - - [Inject] - public IReportReader ReportReader { get; set; } - - private string filterRunners = string.Empty; - private int reportCount; - - [QueryStringParameter("rc")] - public int ReportCount - { - get => this.reportCount; - set - { - if (value > 0) - this.reportCount = value; - } - } - - private int skipReports; - - [QueryStringParameter("rs")] - public int SkipReports - { - get => this.skipReports; - set - { - if (value >= 0) - this.skipReports = value; - } - } - - [QueryStringParameter("fr")] - public string FilterRunners { - get => filterRunners; - set - { - filterRunners = value ?? string.Empty; - - // The following smells up to high heaven, and I don't like it. Sadly, using a @bind is the only way I - // could find to reliably link the value that is shown in the runner drop-down (i.e. the selected value) - // to the one that is actually chosen. And in the context of the @bind, this seems to be the way to kick - // off a load of the correct summaries and a rerender, in the background. - if (summaries != null) - { - new Task(async () => - { - await LoadSummaries(); - StateHasChanged(); - }).Start(); - } - } - } - - private Runner[] runners = null; - private ReportSummary[] summaries = null; - private int totalReports = 0; - private int newReportCount; - private int pageNumber = 1; - private int pageCount = 1; - - public override Task SetParametersAsync(ParameterView parameters) - { - ReportCount = Configuration.GetValue(Constants.ReportCount, 50); - SkipReports = 0; - SortColumn = "dt"; - SortDescending = true; - - return base.SetParametersAsync(parameters); - } - - protected override async Task OnInitializedAsync() - { - this.runners = await ReportReader.GetRunners(); - SkipReports -= SkipReports % ReportCount; - await LoadSummaries(); - this.newReportCount = ReportCount; - } - - private async Task ApplyNewReportCount(int reportCount) - { - this.newReportCount = reportCount; - await ApplyNewReportCount(); - } - - private async Task ApplyNewReportCount() - { - ReportCount = this.newReportCount; - SkipReports -= SkipReports % ReportCount; - - if (summaries != null) - await LoadSummaries(); - } - - private async Task ApplyPageNumber(int pageNumber) - { - SkipReports = (pageNumber - 1) * ReportCount; - - if (summaries != null) - await LoadSummaries(); - } - - private async Task LoadSummaries() - { - (this.summaries, this.totalReports) = await ReportReader.GetSummaries(FilterRunners, SkipReports, ReportCount); - - // adjust SkipReports if we're skipping all reports that we have - if (this.totalReports > 0 && SkipReports >= this.totalReports) - { - SkipReports = this.totalReports - 1 - ((this.totalReports - 1) % ReportCount); - (this.summaries, this.totalReports) = await ReportReader.GetSummaries(FilterRunners, SkipReports, ReportCount); - } - - this.pageNumber = this.SkipReports / this.ReportCount + 1; - this.pageCount = this.totalReports / this.ReportCount; - if (this.totalReports % this.ReportCount > 0) - this.pageCount++; - } - - private void LoadReport(string reportId) - { - NavigationManager.NavigateTo($"report?id={reportId}"); - } - - private async Task Refresh() - { - ReportReader.FlushCache(); - this.runners = await ReportReader.GetRunners(); - await LoadSummaries(); - } - } + public partial class Index + { + [Inject] + public IConfiguration Configuration { get; set; } + + [Inject] + public IReportReader ReportReader { get; set; } + + private string filterRunners = string.Empty; + private int reportCount; + + [QueryStringParameter("rc")] + public int ReportCount + { + get => this.reportCount; + set + { + if (value > 0) + this.reportCount = value; + } + } + + private int skipReports; + + [QueryStringParameter("rs")] + public int SkipReports + { + get => this.skipReports; + set + { + if (value >= 0) + this.skipReports = value; + } + } + + [QueryStringParameter("fr")] + public string FilterRunners + { + get => filterRunners; + set + { + filterRunners = value ?? string.Empty; + + // The following smells up to high heaven, and I don't like it. Sadly, using a @bind is the only way I + // could find to reliably link the value that is shown in the runner drop-down (i.e. the selected value) + // to the one that is actually chosen. And in the context of the @bind, this seems to be the way to kick + // off a load of the correct summaries and a rerender, in the background. + if (summaries != null) + { + new Task(async () => + { + await LoadSummaries(); + StateHasChanged(); + }).Start(); + } + } + } + + private Runner[] runners = null; + private ReportSummary[] summaries = null; + private int totalReports = 0; + private int newReportCount; + private int pageNumber = 1; + private int pageCount = 1; + + public override Task SetParametersAsync(ParameterView parameters) + { + ReportCount = Configuration.GetValue(Constants.ReportCount, 50); + SkipReports = 0; + SortColumn = "dt"; + SortDescending = true; + + return base.SetParametersAsync(parameters); + } + + protected override async Task OnInitializedAsync() + { + this.runners = await ReportReader.GetRunners(); + SkipReports -= SkipReports % ReportCount; + await LoadSummaries(); + this.newReportCount = ReportCount; + } + + private async Task ApplyNewReportCount(int reportCount) + { + this.newReportCount = reportCount; + await ApplyNewReportCount(); + } + + private async Task ApplyNewReportCount() + { + ReportCount = this.newReportCount; + SkipReports -= SkipReports % ReportCount; + + if (summaries != null) + await LoadSummaries(); + } + + private async Task ApplyPageNumber(int pageNumber) + { + SkipReports = (pageNumber - 1) * ReportCount; + + if (summaries != null) + await LoadSummaries(); + } + + private async Task LoadSummaries() + { + (this.summaries, this.totalReports) = await ReportReader.GetSummaries(FilterRunners, SkipReports, ReportCount); + + // adjust SkipReports if we're skipping all reports that we have + if (this.totalReports > 0 && SkipReports >= this.totalReports) + { + SkipReports = this.totalReports - 1 - ((this.totalReports - 1) % ReportCount); + (this.summaries, this.totalReports) = await ReportReader.GetSummaries(FilterRunners, SkipReports, ReportCount); + } + + this.pageNumber = this.SkipReports / this.ReportCount + 1; + this.pageCount = this.totalReports / this.ReportCount; + if (this.totalReports % this.ReportCount > 0) + this.pageCount++; + } + + private void LoadReport(string reportId) + { + NavigationManager.NavigateTo($"report?id={reportId}"); + } + + private async Task Refresh() + { + ReportReader.FlushCache(); + this.runners = await ReportReader.GetRunners(); + await LoadSummaries(); + } + } } diff --git a/src/Frontend/Pages/ReportDetails.razor.cs b/src/Frontend/Pages/ReportDetails.razor.cs index ec7d686..9e00c71 100644 --- a/src/Frontend/Pages/ReportDetails.razor.cs +++ b/src/Frontend/Pages/ReportDetails.razor.cs @@ -18,391 +18,386 @@ namespace PrimeView.Frontend.Pages { - public partial class ReportDetails : SortedTablePage, IResultFilterPropertyProvider, ILanguageInfoProvider - { - private const string FilterPresetStorageKey = "ResultFilterPresets"; + public partial class ReportDetails : SortedTablePage, IResultFilterPropertyProvider, ILanguageInfoProvider + { + private const string FilterPresetStorageKey = "ResultFilterPresets"; - [Inject] - public HttpClient Http { get; set; } + [Inject] + public HttpClient Http { get; set; } - [Inject] - public IConfiguration Configuration { get; set; } + [Inject] + public IConfiguration Configuration { get; set; } - [Inject] - public IReportReader ReportReader { get; set; } + [Inject] + public IReportReader ReportReader { get; set; } - [QueryStringParameter("hi")] - public bool HideSystemInformation { get; set; } = false; - - [QueryStringParameter("hf")] - public bool HideFilters { get; set; } = false; + [QueryStringParameter("hi")] + public bool HideSystemInformation { get; set; } = false; + + [QueryStringParameter("hf")] + public bool HideFilters { get; set; } = false; - [QueryStringParameter("hp")] - public bool HideFilterPresets { get; set; } = false; + [QueryStringParameter("hp")] + public bool HideFilterPresets { get; set; } = false; - [QueryStringParameter("id")] - public string ReportId { get; set; } + [QueryStringParameter("id")] + public string ReportId { get; set; } - [QueryStringParameter("fi")] - public string FilterLanguageText { get; set; } = string.Empty; - - [QueryStringParameter("fp")] - public string FilterParallelismText - { - get => JoinFilterValueString(!FilterParallelSinglethreaded, Constants.SinglethreadedTag, !FilterParallelMultithreaded, Constants.MultithreadedTag); - - set - { - var values = value.SplitFilterValues(); - - FilterParallelSinglethreaded = !values.Contains(Constants.SinglethreadedTag); - FilterParallelMultithreaded = !values.Contains(Constants.MultithreadedTag); - } - } + [QueryStringParameter("fi")] + public string FilterLanguageText { get; set; } = string.Empty; + + [QueryStringParameter("fp")] + public string FilterParallelismText + { + get => JoinFilterValueString(!FilterParallelSinglethreaded, Constants.SinglethreadedTag, !FilterParallelMultithreaded, Constants.MultithreadedTag); + + set + { + var values = value.SplitFilterValues(); + + FilterParallelSinglethreaded = !values.Contains(Constants.SinglethreadedTag); + FilterParallelMultithreaded = !values.Contains(Constants.MultithreadedTag); + } + } - [QueryStringParameter("fa")] - public string FilterAlgorithmText - { - get => JoinFilterValueString(!FilterAlgorithmBase, Constants.BaseTag, !FilterAlgorithmWheel, Constants.WheelTag, !FilterAlgorithmOther, Constants.OtherTag); + [QueryStringParameter("fa")] + public string FilterAlgorithmText + { + get => JoinFilterValueString(!FilterAlgorithmBase, Constants.BaseTag, !FilterAlgorithmWheel, Constants.WheelTag, !FilterAlgorithmOther, Constants.OtherTag); - set - { - var values = value.SplitFilterValues(); - - FilterAlgorithmBase = !values.Contains(Constants.BaseTag); - FilterAlgorithmWheel = !values.Contains(Constants.WheelTag); - FilterAlgorithmOther = !values.Contains(Constants.OtherTag); - } - } - - [QueryStringParameter("ff")] - public string FilterFaithfulText - { - get => JoinFilterValueString(!FilterFaithful, Constants.FaithfulTag, !FilterUnfaithful, Constants.UnfaithfulTag); - - set - { - var values = value.SplitFilterValues(); - - FilterFaithful = !values.Contains(Constants.FaithfulTag); - FilterUnfaithful = !values.Contains(Constants.UnfaithfulTag); - } - } - - [QueryStringParameter("fb")] - public string FilterBitsText - { - get => JoinFilterValueString(!FilterBitsUnknown, Constants.UnknownTag, !FilterBitsOne, Constants.OneTag, !FilterBitsOther, Constants.OtherTag); - - set - { - var values = value.SplitFilterValues(); - - FilterBitsUnknown = !values.Contains(Constants.UnknownTag); - FilterBitsOne = !values.Contains(Constants.OneTag); - FilterBitsOther = !values.Contains(Constants.OtherTag); - } - } - - [QueryStringParameter("tp")] - public bool OnlyHighestPassesPerSecondPerThreadPerLanguage { get; set; } = false; - - public IList FilterLanguages - => FilterLanguageText.SplitFilterValues(); - - public bool FilterParallelSinglethreaded { get; set; } = true; - public bool FilterParallelMultithreaded { get; set; } = true; - - public bool FilterAlgorithmBase { get; set; } = true; - public bool FilterAlgorithmWheel { get; set; } = true; - public bool FilterAlgorithmOther { get; set; } = true; - - public bool FilterFaithful { get; set; } = true; - public bool FilterUnfaithful { get; set; } = true; - - public bool FilterBitsUnknown { get; set; } = true; - public bool FilterBitsOne { get; set; } = true; - public bool FilterBitsOther { get; set; } = true; - - private bool AreFiltersClear - => FilterLanguageText == string.Empty - && FilterParallelismText == string.Empty - && FilterAlgorithmText == string.Empty - && FilterFaithfulText == string.Empty - && FilterBitsText == string.Empty; - - private string ReportTitle - { - get - { - StringBuilder titleBuilder = new(); - - if (report?.User != null) - titleBuilder.Append($" by {report.User}"); - - if (report?.Date != null) - titleBuilder.Append($" at {report.Date.Value.ToLocalTime()}"); - - return titleBuilder.Length > 0 ? $"Report generated{titleBuilder}" : "Report"; - } - } - - private string ReportFileBaseName - { - get - { - StringBuilder fileNameBuilder = new("primes_report"); - - if (report?.User != null) - fileNameBuilder.Append($"_{report.User.Replace(' ', '_')}"); - - if (report?.Date != null) - fileNameBuilder.Append($"_{report.Date.Value.ToLocalTime().ToString().Replace(' ', '_')}"); - - return fileNameBuilder.ToString(); - } - } - - private string solutionUrlTemplate; - private Report report = null; - private int rowNumber = 0; - private Dictionary languageMap = null; - private List filterPresets = null; - private string filterPresetName; - - [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE0044:Add readonly modifier", Justification = "Used as @ref in razor file")] - private ElementReference languagesSelect; - - public override Task SetParametersAsync(ParameterView parameters) - { - SortColumn = "pp"; - SortDescending = true; - - return base.SetParametersAsync(parameters); - } - - protected override async Task OnInitializedAsync() - { - this.solutionUrlTemplate = Configuration.GetValue(Constants.SolutionUrlTemplate, null); - this.report = await ReportReader.GetReport(ReportId); - await LoadLanguageMap(); - - if (this.solutionUrlTemplate != null) - { - foreach (var result in this.report.Results) - { - var languageInfo = GetLanguageInfo(result.Language); - - result.SolutionUrl = this.solutionUrlTemplate - .Replace("{sln}", result.Solution) - .Replace("{tag}", languageInfo.Tag ?? languageInfo.Name); - } - } - - if (LocalStorage.ContainKey(FilterPresetStorageKey)) - { - try - { - this.filterPresets = LocalStorage.GetItem>(FilterPresetStorageKey); - } - catch - { - LocalStorage.RemoveItem(FilterPresetStorageKey); - } - } - - if (this.filterPresets == null) - this.filterPresets = new(); - - InsertFilterPreset(new LeaderboardFilterPreset()); - InsertFilterPreset(new MultithreadedLeaderboardFilterPreset()); - - await base.OnInitializedAsync(); - } - - private async Task ClearFilters() - { - FilterLanguageText = string.Empty; - FilterParallelismText = string.Empty; - FilterAlgorithmText = string.Empty; - FilterFaithfulText = string.Empty; - FilterBitsText = string.Empty; - - await JSRuntime.InvokeVoidAsync("PrimeViewJS.ClearMultiselectValues", languagesSelect); - } - - private void ToggleSystemInfoPanel() - { - HideSystemInformation = !HideSystemInformation; - } - - private void ToggleFilterPanel() - { - HideFilters = !HideFilters; - } - - private void ToggleFilterPresetPanel() - { - HideFilterPresets = !HideFilterPresets; - } - - private async Task LoadLanguageMap() - { - try - { - this.languageMap = await Http.GetFromJsonAsync>("data/langmap.json"); - foreach (var entry in languageMap) - { - entry.Value.Key = entry.Key; - } - } - catch { } - } - - protected override void OnTableRefreshStart() - { - rowNumber = this.sortedTable.PageNumber * this.sortedTable.PageSize; - - base.OnTableRefreshStart(); - } - - public LanguageInfo GetLanguageInfo(string language) - { - if (this.languageMap == null) - this.languageMap = new(); - - if (languageMap.ContainsKey(language)) - return this.languageMap[language]; - - LanguageInfo info = new() { Key = language, Name = language[0].ToString().ToUpper() + language[1..] }; - - this.languageMap[language] = info; - - return info; - } - - private async Task LanguageSelectionChanged() - { - FilterLanguageText = await JSRuntime.InvokeAsync("PrimeViewJS.GetMultiselectValues", languagesSelect, "~") ?? string.Empty; - } - - private static string JoinFilterValueString(params object[] flagSet) - { - List setFlags = new(); - - for (int i = 0; i < flagSet.Length; i += 2) - { - if ((bool)flagSet[i]) - setFlags.Add(flagSet[i + 1].ToString()); - } - - return setFlags.JoinFilterValues(); - } - - private bool IsFilterPresetNameValid(string name) - { - return !string.IsNullOrWhiteSpace(name) - && (filterPresets == null || !filterPresets.Any(preset => preset.IsFixed && string.Equals(preset.Name, name, StringComparison.OrdinalIgnoreCase))); - } - - private async Task ApplyFilterPreset(int index) - { - var preset = this.filterPresets?[index]; - - if (preset == null) - return; - - FilterAlgorithmText = preset.AlgorithmText; - FilterBitsText = preset.BitsText; - FilterFaithfulText = preset.FaithfulText; - FilterLanguageText = preset.ImplementationText; - FilterParallelismText = preset.ParallelismText; - - var filterImplementations = FilterLanguages; - - if (filterImplementations.Count > 0) - await JSRuntime.InvokeVoidAsync("PrimeViewJS.SetMultiselectValues", languagesSelect, FilterLanguages.ToArray()); - - else - await JSRuntime.InvokeVoidAsync("PrimeViewJS.ClearMultiselectValues", languagesSelect); - - this.filterPresetName = preset.IsFixed ? string.Empty : preset.Name; - } - - private void RemoveFilterPreset(int index) - { - this.filterPresets?.RemoveAt(index); - - SaveFilterPresets(); - } - - private void InsertFilterPreset(ResultFilterPreset preset) - { - if (this.filterPresets == null) - this.filterPresets = new(); - - int i; - for (i = 0; i < this.filterPresets.Count && string.Compare(preset.Name, this.filterPresets[i].Name, StringComparison.OrdinalIgnoreCase) > 0; i++) ; - - if (i < this.filterPresets.Count && string.Equals(preset.Name, this.filterPresets[i].Name, StringComparison.OrdinalIgnoreCase)) - { - if (this.filterPresets[i].IsFixed) - return; - - this.filterPresets.RemoveAt(i); - } + set + { + var values = value.SplitFilterValues(); + + FilterAlgorithmBase = !values.Contains(Constants.BaseTag); + FilterAlgorithmWheel = !values.Contains(Constants.WheelTag); + FilterAlgorithmOther = !values.Contains(Constants.OtherTag); + } + } + + [QueryStringParameter("ff")] + public string FilterFaithfulText + { + get => JoinFilterValueString(!FilterFaithful, Constants.FaithfulTag, !FilterUnfaithful, Constants.UnfaithfulTag); + + set + { + var values = value.SplitFilterValues(); + + FilterFaithful = !values.Contains(Constants.FaithfulTag); + FilterUnfaithful = !values.Contains(Constants.UnfaithfulTag); + } + } + + [QueryStringParameter("fb")] + public string FilterBitsText + { + get => JoinFilterValueString(!FilterBitsUnknown, Constants.UnknownTag, !FilterBitsOne, Constants.OneTag, !FilterBitsOther, Constants.OtherTag); + + set + { + var values = value.SplitFilterValues(); + + FilterBitsUnknown = !values.Contains(Constants.UnknownTag); + FilterBitsOne = !values.Contains(Constants.OneTag); + FilterBitsOther = !values.Contains(Constants.OtherTag); + } + } + + [QueryStringParameter("tp")] + public bool OnlyHighestPassesPerSecondPerThreadPerLanguage { get; set; } = false; + + public IList FilterLanguages + => FilterLanguageText.SplitFilterValues(); + + public bool FilterParallelSinglethreaded { get; set; } = true; + public bool FilterParallelMultithreaded { get; set; } = true; + + public bool FilterAlgorithmBase { get; set; } = true; + public bool FilterAlgorithmWheel { get; set; } = true; + public bool FilterAlgorithmOther { get; set; } = true; + + public bool FilterFaithful { get; set; } = true; + public bool FilterUnfaithful { get; set; } = true; + + public bool FilterBitsUnknown { get; set; } = true; + public bool FilterBitsOne { get; set; } = true; + public bool FilterBitsOther { get; set; } = true; + + private bool AreFiltersClear + => FilterLanguageText == string.Empty + && FilterParallelismText == string.Empty + && FilterAlgorithmText == string.Empty + && FilterFaithfulText == string.Empty + && FilterBitsText == string.Empty; + + private string ReportTitle + { + get + { + StringBuilder titleBuilder = new(); + + if (report?.User != null) + titleBuilder.Append($" by {report.User}"); + + if (report?.Date != null) + titleBuilder.Append($" at {report.Date.Value.ToLocalTime()}"); + + return titleBuilder.Length > 0 ? $"Report generated{titleBuilder}" : "Report"; + } + } + + private string ReportFileBaseName + { + get + { + StringBuilder fileNameBuilder = new("primes_report"); + + if (report?.User != null) + fileNameBuilder.Append($"_{report.User.Replace(' ', '_')}"); + + if (report?.Date != null) + fileNameBuilder.Append($"_{report.Date.Value.ToLocalTime().ToString().Replace(' ', '_')}"); + + return fileNameBuilder.ToString(); + } + } + + private string solutionUrlTemplate; + private Report report = null; + private int rowNumber = 0; + private Dictionary languageMap = null; + private List filterPresets = null; + private string filterPresetName; + private ElementReference languagesSelect; + + public override Task SetParametersAsync(ParameterView parameters) + { + SortColumn = "pp"; + SortDescending = true; + + return base.SetParametersAsync(parameters); + } + + protected override async Task OnInitializedAsync() + { + this.solutionUrlTemplate = Configuration.GetValue(Constants.SolutionUrlTemplate, null); + this.report = await ReportReader.GetReport(ReportId); + await LoadLanguageMap(); + + if (this.solutionUrlTemplate != null) + { + foreach (var result in this.report.Results) + { + var languageInfo = GetLanguageInfo(result.Language); + + result.SolutionUrl = this.solutionUrlTemplate + .Replace("{sln}", result.Solution) + .Replace("{tag}", languageInfo.Tag ?? languageInfo.Name); + } + } + + if (LocalStorage.ContainKey(FilterPresetStorageKey)) + { + try + { + this.filterPresets = LocalStorage.GetItem>(FilterPresetStorageKey); + } + catch + { + LocalStorage.RemoveItem(FilterPresetStorageKey); + } + } + + this.filterPresets ??= []; + + InsertFilterPreset(new LeaderboardFilterPreset()); + InsertFilterPreset(new MultithreadedLeaderboardFilterPreset()); + + await base.OnInitializedAsync(); + } + + private async Task ClearFilters() + { + FilterLanguageText = string.Empty; + FilterParallelismText = string.Empty; + FilterAlgorithmText = string.Empty; + FilterFaithfulText = string.Empty; + FilterBitsText = string.Empty; + + await JSRuntime.InvokeVoidAsync("PrimeViewJS.ClearMultiselectValues", languagesSelect); + } + + private void ToggleSystemInfoPanel() + { + HideSystemInformation = !HideSystemInformation; + } + + private void ToggleFilterPanel() + { + HideFilters = !HideFilters; + } + + private void ToggleFilterPresetPanel() + { + HideFilterPresets = !HideFilterPresets; + } + + private async Task LoadLanguageMap() + { + try + { + this.languageMap = await Http.GetFromJsonAsync>("data/langmap.json"); + foreach (var entry in languageMap) + { + entry.Value.Key = entry.Key; + } + } + catch { } + } + + protected override void OnTableRefreshStart() + { + rowNumber = this.sortedTable.PageNumber * this.sortedTable.PageSize; + + base.OnTableRefreshStart(); + } + + public LanguageInfo GetLanguageInfo(string language) + { + this.languageMap ??= []; + + if (languageMap.TryGetValue(language, out LanguageInfo value)) + return value; + + LanguageInfo info = new() { Key = language, Name = language[0].ToString().ToUpper() + language[1..] }; + + this.languageMap[language] = info; + + return info; + } + + private async Task LanguageSelectionChanged() + { + FilterLanguageText = await JSRuntime.InvokeAsync("PrimeViewJS.GetMultiselectValues", languagesSelect, "~") ?? string.Empty; + } + + private static string JoinFilterValueString(params object[] flagSet) + { + List setFlags = []; + + for (int i = 0; i < flagSet.Length; i += 2) + { + if ((bool)flagSet[i]) + setFlags.Add(flagSet[i + 1].ToString()); + } + + return setFlags.JoinFilterValues(); + } + + private bool IsFilterPresetNameValid(string name) + { + return !string.IsNullOrWhiteSpace(name) + && (filterPresets == null || !filterPresets.Any(preset => preset.IsFixed && string.Equals(preset.Name, name, StringComparison.OrdinalIgnoreCase))); + } + + private async Task ApplyFilterPreset(int index) + { + var preset = this.filterPresets?[index]; + + if (preset == null) + return; + + FilterAlgorithmText = preset.AlgorithmText; + FilterBitsText = preset.BitsText; + FilterFaithfulText = preset.FaithfulText; + FilterLanguageText = preset.ImplementationText; + FilterParallelismText = preset.ParallelismText; + + var filterImplementations = FilterLanguages; + + if (filterImplementations.Count > 0) + await JSRuntime.InvokeVoidAsync("PrimeViewJS.SetMultiselectValues", languagesSelect, FilterLanguages.ToArray()); + + else + await JSRuntime.InvokeVoidAsync("PrimeViewJS.ClearMultiselectValues", languagesSelect); + + this.filterPresetName = preset.IsFixed ? string.Empty : preset.Name; + } + + private void RemoveFilterPreset(int index) + { + this.filterPresets?.RemoveAt(index); + + SaveFilterPresets(); + } + + private void InsertFilterPreset(ResultFilterPreset preset) + { + this.filterPresets ??= []; + + int i; + for (i = 0; i < this.filterPresets.Count && string.Compare(preset.Name, this.filterPresets[i].Name, StringComparison.OrdinalIgnoreCase) > 0; i++) ; + + if (i < this.filterPresets.Count && string.Equals(preset.Name, this.filterPresets[i].Name, StringComparison.OrdinalIgnoreCase)) + { + if (this.filterPresets[i].IsFixed) + return; + + this.filterPresets.RemoveAt(i); + } - this.filterPresets.Insert(i, preset); + this.filterPresets.Insert(i, preset); - SaveFilterPresets(); - } + SaveFilterPresets(); + } - private void AddFilterPreset() - { - if (string.IsNullOrWhiteSpace(this.filterPresetName)) - return; + private void AddFilterPreset() + { + if (string.IsNullOrWhiteSpace(this.filterPresetName)) + return; - this.filterPresetName = this.filterPresetName.Trim(); + this.filterPresetName = this.filterPresetName.Trim(); - InsertFilterPreset(new() - { - Name = this.filterPresetName, - AlgorithmText = FilterAlgorithmText, - BitsText = FilterBitsText, - FaithfulText = FilterFaithfulText, - ImplementationText = FilterLanguageText, - ParallelismText = FilterParallelismText - }); + InsertFilterPreset(new() + { + Name = this.filterPresetName, + AlgorithmText = FilterAlgorithmText, + BitsText = FilterBitsText, + FaithfulText = FilterFaithfulText, + ImplementationText = FilterLanguageText, + ParallelismText = FilterParallelismText + }); - this.filterPresetName = null; - } + this.filterPresetName = null; + } - private void SaveFilterPresets() - { - LocalStorage.SetItem(FilterPresetStorageKey, filterPresets.Where(preset => !preset.IsFixed)); - } - - private async Task DownloadExport(string fileExtension, string mimeType, byte[] export) - { - using DotNetStreamReference streamRef = new(new MemoryStream(export)); + private void SaveFilterPresets() + { + LocalStorage.SetItem(FilterPresetStorageKey, filterPresets.Where(preset => !preset.IsFixed)); + } - await JSRuntime.InvokeVoidAsync("PrimeViewJS.DownloadFileFromStream", ReportFileBaseName + fileExtension, mimeType, streamRef); - } + private async Task DownloadExport(string fileExtension, string mimeType, byte[] export) + { + using DotNetStreamReference streamRef = new(new MemoryStream(export)); - private async Task DownloadJson() - { - byte[] export = JsonConverter.Convert(this.report); + await JSRuntime.InvokeVoidAsync("PrimeViewJS.DownloadFileFromStream", ReportFileBaseName + fileExtension, mimeType, streamRef); + } - if (export != null) - await DownloadExport(".json", "application/json", export); - } + private async Task DownloadJson() + { + byte[] export = JsonConverter.Convert(this.report); - private async Task DownloadExcel() - { - byte[] export = ExcelConverter.Convert(this.report, this); + if (export != null) + await DownloadExport(".json", "application/json", export); + } - if (export != null) - await DownloadExport(".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", export); - } - } + private async Task DownloadExcel() + { + byte[] export = ExcelConverter.Convert(this.report, this); + + if (export != null) + await DownloadExport(".xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", export); + } + } } diff --git a/src/Frontend/Parameters/PropertyParameterMap.cs b/src/Frontend/Parameters/PropertyParameterMap.cs index 178bfe0..e21661e 100644 --- a/src/Frontend/Parameters/PropertyParameterMap.cs +++ b/src/Frontend/Parameters/PropertyParameterMap.cs @@ -6,73 +6,73 @@ namespace PrimeView.Frontend.Parameters { - public static class PropertyParameterMap - { - private static readonly Dictionary map; + public static class PropertyParameterMap + { + private static readonly Dictionary map; - static PropertyParameterMap() - { - string result = nameof(Result); - string reportSummary = nameof(ReportSummary); + static PropertyParameterMap() + { + string result = nameof(Result); + string reportSummary = nameof(ReportSummary); - map = new() - { - { $"{result}.{nameof(Result.Language)}", "im" }, - { $"{result}.{nameof(Result.Solution)}", "so" }, - { $"{result}.{nameof(Result.Label)}", "la" }, - { $"{result}.{nameof(Result.Passes)}", "ps" }, - { $"{result}.{nameof(Result.Duration)}", "du" }, - { $"{result}.{nameof(Result.Threads)}", "td" }, - { $"{result}.{nameof(Result.Algorithm)}", "al" }, - { $"{result}.{nameof(Result.IsFaithful)}", "ff" }, - { $"{result}.{nameof(Result.Bits)}", "bt" }, - { $"{result}.{nameof(Result.PassesPerSecond)}", "pp" }, - { $"{result}.{nameof(Result.IsMultiThreaded)}", "mt" }, - { $"{reportSummary}.{nameof(ReportSummary.Date)}", "dt" }, - { $"{reportSummary}.{nameof(ReportSummary.User)}", "us" }, - { $"{reportSummary}.{nameof(ReportSummary.CpuVendor)}", "cv" }, - { $"{reportSummary}.{nameof(ReportSummary.CpuBrand)}", "cb" }, - { $"{reportSummary}.{nameof(ReportSummary.CpuCores)}", "cc" }, - { $"{reportSummary}.{nameof(ReportSummary.CpuProcessors)}", "cp" }, - { $"{reportSummary}.{nameof(ReportSummary.OsDistro)}", "od" }, - { $"{reportSummary}.{nameof(ReportSummary.OsPlatform)}", "op" }, - { $"{reportSummary}.{nameof(ReportSummary.OsRelease)}", "or" }, - { $"{reportSummary}.{nameof(ReportSummary.Architecture)}", "ar" }, - { $"{reportSummary}.{nameof(ReportSummary.IsSystemVirtual)}", "sv" }, - { $"{reportSummary}.{nameof(ReportSummary.DockerArchitecture)}", "da" }, - { $"{reportSummary}.{nameof(ReportSummary.ResultCount)}", "rc" } - }; - } + map = new() + { + { $"{result}.{nameof(Result.Language)}", "im" }, + { $"{result}.{nameof(Result.Solution)}", "so" }, + { $"{result}.{nameof(Result.Label)}", "la" }, + { $"{result}.{nameof(Result.Passes)}", "ps" }, + { $"{result}.{nameof(Result.Duration)}", "du" }, + { $"{result}.{nameof(Result.Threads)}", "td" }, + { $"{result}.{nameof(Result.Algorithm)}", "al" }, + { $"{result}.{nameof(Result.IsFaithful)}", "ff" }, + { $"{result}.{nameof(Result.Bits)}", "bt" }, + { $"{result}.{nameof(Result.PassesPerSecond)}", "pp" }, + { $"{result}.{nameof(Result.IsMultiThreaded)}", "mt" }, + { $"{reportSummary}.{nameof(ReportSummary.Date)}", "dt" }, + { $"{reportSummary}.{nameof(ReportSummary.User)}", "us" }, + { $"{reportSummary}.{nameof(ReportSummary.CpuVendor)}", "cv" }, + { $"{reportSummary}.{nameof(ReportSummary.CpuBrand)}", "cb" }, + { $"{reportSummary}.{nameof(ReportSummary.CpuCores)}", "cc" }, + { $"{reportSummary}.{nameof(ReportSummary.CpuProcessors)}", "cp" }, + { $"{reportSummary}.{nameof(ReportSummary.OsDistro)}", "od" }, + { $"{reportSummary}.{nameof(ReportSummary.OsPlatform)}", "op" }, + { $"{reportSummary}.{nameof(ReportSummary.OsRelease)}", "or" }, + { $"{reportSummary}.{nameof(ReportSummary.Architecture)}", "ar" }, + { $"{reportSummary}.{nameof(ReportSummary.IsSystemVirtual)}", "sv" }, + { $"{reportSummary}.{nameof(ReportSummary.DockerArchitecture)}", "da" }, + { $"{reportSummary}.{nameof(ReportSummary.ResultCount)}", "rc" } + }; + } - public static string GetPropertyParameterName(string propertyName) - { - if (propertyName == null) - return null; + public static string GetPropertyParameterName(string propertyName) + { + if (propertyName == null) + return null; - string name = $"{typeof(T).Name}.{propertyName}"; + string name = $"{typeof(T).Name}.{propertyName}"; - return map.ContainsKey(name) ? map[name] : null; - } + return map.TryGetValue(name, out string value) ? value : null; + } - public static string GetPropertyParameterName(this Expression> expression) - { - if (expression == null) - return null; + public static string GetPropertyParameterName(this Expression> expression) + { + if (expression == null) + return null; - if (expression.Body is not MemberExpression body) - { - UnaryExpression ubody = (UnaryExpression)expression.Body; - body = ubody.Operand as MemberExpression; - } + if (expression.Body is not MemberExpression body) + { + UnaryExpression ubody = (UnaryExpression)expression.Body; + body = ubody.Operand as MemberExpression; + } - MemberInfo memberInfo = body?.Member; + MemberInfo memberInfo = body?.Member; - if (memberInfo == null) - return null; + if (memberInfo == null) + return null; - string name = $"{memberInfo.DeclaringType.Name}.{memberInfo.Name}"; + string name = $"{memberInfo.DeclaringType.Name}.{memberInfo.Name}"; - return map.ContainsKey(name) ? map[name] : null; - } - } + return map.TryGetValue(name, out string value) ? value : null; + } + } } diff --git a/src/Frontend/Parameters/QueryStringParameterAttribute.cs b/src/Frontend/Parameters/QueryStringParameterAttribute.cs index 694e9e4..db3c644 100644 --- a/src/Frontend/Parameters/QueryStringParameterAttribute.cs +++ b/src/Frontend/Parameters/QueryStringParameterAttribute.cs @@ -2,16 +2,16 @@ namespace PrimeView.Frontend.Parameters { - [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] - public sealed class QueryStringParameterAttribute : Attribute - { - public QueryStringParameterAttribute() { } + [AttributeUsage(AttributeTargets.Property, AllowMultiple = false, Inherited = true)] + public sealed class QueryStringParameterAttribute : Attribute + { + public QueryStringParameterAttribute() { } - public QueryStringParameterAttribute(string name) - { - Name = name; - } + public QueryStringParameterAttribute(string name) + { + Name = name; + } - public string Name { get; } - } + public string Name { get; } + } } diff --git a/src/Frontend/Parameters/QueryStringParameterExtensions.cs b/src/Frontend/Parameters/QueryStringParameterExtensions.cs index 07447d9..c762a43 100644 --- a/src/Frontend/Parameters/QueryStringParameterExtensions.cs +++ b/src/Frontend/Parameters/QueryStringParameterExtensions.cs @@ -10,134 +10,133 @@ namespace PrimeView.Frontend.Parameters { - public static class QueryStringParameterExtensions - { - // Apply the values from the query string to the current component - public static void SetParametersFromQueryString(this ComponentBase component, NavigationManager navigationManager, ISyncLocalStorageService localStorage) - { - if (!Uri.TryCreate(navigationManager.Uri, UriKind.RelativeOrAbsolute, out var uri)) - return; - - // Parse the query string - Dictionary queryString = QueryHelpers.ParseQuery(uri.Query); - Dictionary storedParameters = null; - - string storageKey = GetLocalStorageKey(component); - - if (localStorage.ContainKey(storageKey)) - { - try - { - storedParameters = localStorage.GetItem>(storageKey); - } - catch - { - localStorage.RemoveItem(storageKey); - } - } - - if (storedParameters == null) - storedParameters = new(); - - // Enumerate all properties of the component - foreach (var property in GetProperties(component)) - { - // Get the name of the parameter to read from the query string - var parameterName = GetQueryStringParameterName(property); - if (parameterName == null) - continue; // The property is not decorated by [QueryStringParameterAttribute] - - if (!queryString.TryGetValue(parameterName, out StringValues value) && storedParameters.ContainsKey(parameterName)) - value = storedParameters[parameterName]; - - if (!StringValues.IsNullOrEmpty(value)) - { - // Convert the value from string to the actual property type - var convertedValue = ConvertValue(value, property.PropertyType); - property.SetValue(component, convertedValue); - } - } - } - - // Apply the values from the component to the query string - public static void UpdateQueryString(this ComponentBase component, NavigationManager navigationManager, ISyncLocalStorageService localStorage, IJSInProcessRuntime runtime) - { - if (!Uri.TryCreate(navigationManager.Uri, UriKind.RelativeOrAbsolute, out var uri)) - uri = new Uri(navigationManager.Uri); - - // Fill the dictionary with the parameters of the component - Dictionary parameters = QueryHelpers.ParseQuery(uri.Query); - Dictionary storedParameters = new(); - - foreach (var property in GetProperties(component)) - { - var parameterName = GetQueryStringParameterName(property); - if (parameterName == null) - continue; - - var value = property.GetValue(component); - - if (value is null) - parameters.Remove(parameterName); - - else - { - var convertedValue = ConvertToString(value); - parameters[parameterName] = convertedValue; - storedParameters[parameterName] = convertedValue; - } - } - - // Compute the new URL - var newUri = uri.GetComponents(UriComponents.Scheme | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped); - foreach (var parameter in parameters) - { - foreach (var value in parameter.Value) - newUri = QueryHelpers.AddQueryString(newUri, parameter.Key, value); - } - - runtime.InvokeVoid("PrimeViewJS.ShowUrl", newUri); - localStorage.SetItem(GetLocalStorageKey(component), storedParameters); - } - - private static string GetLocalStorageKey(ComponentBase component) - { - return $"{component.GetType().Name}QueryParameters"; - } - - private static object ConvertValue(StringValues value, Type type) - { - try - { - return Convert.ChangeType(value[0], type, CultureInfo.InvariantCulture); - } - catch { } - - return GetDefault(type); - } - - private static object GetDefault(Type type) - { - return type.IsValueType ? Activator.CreateInstance(type) : null; - } - - private static string ConvertToString(object value) - { - return Convert.ToString(value, CultureInfo.InvariantCulture); - } - - private static PropertyInfo[] GetProperties(ComponentBase component) - { - return component.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); - } - - private static string GetQueryStringParameterName(PropertyInfo property) - { - var attribute = property.GetCustomAttribute(); - if (attribute == null) - return null; - - return attribute.Name ?? property.Name; - } - } + public static class QueryStringParameterExtensions + { + // Apply the values from the query string to the current component + public static void SetParametersFromQueryString(this ComponentBase component, NavigationManager navigationManager, ISyncLocalStorageService localStorage) + { + if (!Uri.TryCreate(navigationManager.Uri, UriKind.RelativeOrAbsolute, out var uri)) + return; + + // Parse the query string + Dictionary queryString = QueryHelpers.ParseQuery(uri.Query); + Dictionary storedParameters = null; + + string storageKey = GetLocalStorageKey(component); + + if (localStorage.ContainKey(storageKey)) + { + try + { + storedParameters = localStorage.GetItem>(storageKey); + } + catch + { + localStorage.RemoveItem(storageKey); + } + } + + storedParameters ??= []; + + // Enumerate all properties of the component + foreach (var property in GetProperties(component)) + { + // Get the name of the parameter to read from the query string + var parameterName = GetQueryStringParameterName(property); + if (parameterName == null) + continue; // The property is not decorated by [QueryStringParameterAttribute] + + if (!queryString.TryGetValue(parameterName, out StringValues value) && storedParameters.TryGetValue(parameterName, out string storedValue)) + value = storedValue; + + if (!StringValues.IsNullOrEmpty(value)) + { + // Convert the value from string to the actual property type + var convertedValue = ConvertValue(value, property.PropertyType); + property.SetValue(component, convertedValue); + } + } + } + + // Apply the values from the component to the query string + public static void UpdateQueryString(this ComponentBase component, NavigationManager navigationManager, ISyncLocalStorageService localStorage, IJSInProcessRuntime runtime) + { + if (!Uri.TryCreate(navigationManager.Uri, UriKind.RelativeOrAbsolute, out var uri)) + uri = new Uri(navigationManager.Uri); + + // Fill the dictionary with the parameters of the component + Dictionary parameters = QueryHelpers.ParseQuery(uri.Query); + Dictionary storedParameters = []; + + foreach (var property in GetProperties(component)) + { + var parameterName = GetQueryStringParameterName(property); + if (parameterName == null) + continue; + + var value = property.GetValue(component); + + if (value is null) + parameters.Remove(parameterName); + + else + { + var convertedValue = ConvertToString(value); + parameters[parameterName] = convertedValue; + storedParameters[parameterName] = convertedValue; + } + } + + // Compute the new URL + var newUri = uri.GetComponents(UriComponents.Scheme | UriComponents.Host | UriComponents.Port | UriComponents.Path, UriFormat.UriEscaped); + foreach (var parameter in parameters) + { + foreach (var value in parameter.Value) + newUri = QueryHelpers.AddQueryString(newUri, parameter.Key, value); + } + + runtime.InvokeVoid("PrimeViewJS.ShowUrl", newUri); + localStorage.SetItem(GetLocalStorageKey(component), storedParameters); + } + + private static string GetLocalStorageKey(ComponentBase component) + { + return $"{component.GetType().Name}QueryParameters"; + } + + private static object ConvertValue(StringValues value, Type type) + { + try + { + return Convert.ChangeType(value[0], type, CultureInfo.InvariantCulture); + } + catch { } + + return GetDefault(type); + } + + private static object GetDefault(Type type) + { + return type.IsValueType ? Activator.CreateInstance(type) : null; + } + + private static string ConvertToString(object value) + { + return Convert.ToString(value, CultureInfo.InvariantCulture); + } + + private static PropertyInfo[] GetProperties(ComponentBase component) + { + return component.GetType().GetProperties(BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance); + } + + private static string GetQueryStringParameterName(PropertyInfo property) + { + var attribute = property.GetCustomAttribute(); + if (attribute == null) + return null; + + return attribute.Name ?? property.Name; + } + } } diff --git a/src/Frontend/Program.cs b/src/Frontend/Program.cs index a42b917..b3536d3 100644 --- a/src/Frontend/Program.cs +++ b/src/Frontend/Program.cs @@ -14,33 +14,33 @@ namespace PrimeView.Frontend { - public class Program - { - public static async Task Main(string[] args) - { - var builder = WebAssemblyHostBuilder.CreateDefault(args); - builder.RootComponents.Add("#app"); + public class Program + { + public static async Task Main(string[] args) + { + var builder = WebAssemblyHostBuilder.CreateDefault(args); + builder.RootComponents.Add("#app"); - string baseAddress = builder.HostEnvironment.BaseAddress; + string baseAddress = builder.HostEnvironment.BaseAddress; - builder.Services - .AddScoped(sp => new HttpClient { BaseAddress = new Uri(baseAddress) }) - .AddBlazorTable() - .AddBlazoredLocalStorage() - .AddSingleton(services => (IJSInProcessRuntime)services.GetRequiredService()); + builder.Services + .AddScoped(sp => new HttpClient { BaseAddress = new Uri(baseAddress) }) + .AddBlazorTable() + .AddBlazoredLocalStorage() + .AddSingleton(services => (IJSInProcessRuntime)services.GetRequiredService()); - switch (builder.Configuration.GetValue(Constants.ActiveReader)) - { - case Constants.JsonFileReader: - builder.Services.AddJsonFileReportReader(baseAddress, builder.Configuration.GetSection(Constants.Readers).GetSection(Constants.JsonFileReader)); - break; + switch (builder.Configuration.GetValue(Constants.ActiveReader)) + { + case Constants.JsonFileReader: + builder.Services.AddJsonFileReportReader(baseAddress, builder.Configuration.GetSection(Constants.Readers).GetSection(Constants.JsonFileReader)); + break; - case Constants.RestAPIReader: - builder.Services.AddRestAPIReportReader(builder.Configuration.GetSection(Constants.Readers).GetSection(Constants.RestAPIReader)); - break; - } + case Constants.RestAPIReader: + builder.Services.AddRestAPIReportReader(builder.Configuration.GetSection(Constants.Readers).GetSection(Constants.RestAPIReader)); + break; + } - await builder.Build().RunAsync(); - } - } + await builder.Build().RunAsync(); + } + } } diff --git a/src/Frontend/ReportExporters/ExcelConverter.cs b/src/Frontend/ReportExporters/ExcelConverter.cs index 79d5408..be5ef7c 100644 --- a/src/Frontend/ReportExporters/ExcelConverter.cs +++ b/src/Frontend/ReportExporters/ExcelConverter.cs @@ -9,152 +9,152 @@ namespace PrimeView.Frontend.ReportExporters { - public static class ExcelConverter - { - public static byte[] Convert(Report report, ILanguageInfoProvider languageInfoProvider) - { - ExcelPackage.LicenseContext = LicenseContext.NonCommercial; - - using ExcelPackage excelPackage = new(); - - var reportSheet = excelPackage.Workbook.Worksheets.Add($"Report"); - FillReportSheet(reportSheet, report); - - var resultSheet = excelPackage.Workbook.Worksheets.Add($"Results"); - FillResultsSheet(resultSheet, report.Results, languageInfoProvider); - - return excelPackage.GetAsByteArray(); - } - - private static void FillReportSheet(ExcelWorksheet sheet, Report report) - { - int rowNumber = 1; - - AddGeneralSection(sheet, ref rowNumber, report); - AddCPUSection(sheet, ref rowNumber, report.CPU); - AddOperatingSystemSection(sheet, ref rowNumber, report.OperatingSystem); - AddSystemSection(sheet, ref rowNumber, report.System); - AddDockerSection(sheet, ref rowNumber, report.DockerInfo); - - sheet.Column(1).Style.Font.Bold = true; - sheet.Column(2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; - sheet.Columns.Style.VerticalAlignment = ExcelVerticalAlignment.Top; - sheet.Columns.AutoFit(); - } - - private static void AddGeneralSection(ExcelWorksheet sheet, ref int rowNumber, Report report) - { - AddExpandableSection(sheet, ref rowNumber, "General", (sheet, rowNumber) => - { - int sectionTop = rowNumber; - if (AddValue(sheet, ref rowNumber, "Id", report.Id)) - { - sheet.Rows[sectionTop, rowNumber - 1].Hidden = true; - } - AddValue(sheet, ref rowNumber, "User", report.User); - AddValue(sheet, ref rowNumber, "Created at", report.Date, format: "yyyy-mm-dd HH:MM:SS"); - return rowNumber; - }); - } - - private static void AddCPUSection(ExcelWorksheet sheet, ref int rowNumber, CPUInfo cpu) - { - AddExpandableSection(sheet, ref rowNumber, "CPU", (sheet, rowNumber) => - { - AddValues(sheet, ref rowNumber, new() - { - { "Manufacturer", cpu.Manufacturer }, - { "Raspberry processor", cpu.RaspberryProcessor }, - { "Brand", cpu.Brand }, - { "Vendor", cpu.Vendor }, - { "Family", cpu.Family }, - { "Model", cpu.Model }, - { "Stepping", cpu.Stepping }, - { "Revision", cpu.Revision }, - { "# Cores", cpu.Cores }, - { "# Efficiency cores", cpu.EfficiencyCores }, - { "# Performance cores", cpu.PerformanceCores }, - { "# Physical cores", cpu.PhysicalCores }, - { "# Processors", cpu.Processors }, - { "Speed", cpu.Speed }, - { "Minimum speed", cpu.MinimumSpeed }, - { "Maximum speed", cpu.MaximumSpeed }, - { "Voltage", cpu.Voltage }, - { "Governor", cpu.Governor }, - { "Socket", cpu.Socket } - }); - if (cpu.FlagValues != null) - AddValue(sheet, ref rowNumber, "Flags", string.Join(", ", cpu.FlagValues.OrderBy(f => f)), wordWrap: true); - AddValue(sheet, ref rowNumber, "Virtualization", cpu.Virtualization); - if (cpu.Cache != null && cpu.Cache.Count > 0) - { - AddValue(sheet, ref rowNumber, "Cache", force: true); - foreach (var cacheLine in cpu.Cache) - AddValue(sheet, ref rowNumber, $"- {cacheLine.Key}", cacheLine.Value); - } - return rowNumber; - }); - } - - private static void AddOperatingSystemSection(ExcelWorksheet sheet, ref int rowNumber, OperatingSystemInfo os) - { - AddExpandableValuesSection(sheet, ref rowNumber, "Operating System", new() - { - { "Platform", os.Platform }, - { "Distribution", os.Distribution }, - { "Release", os.Release }, - { "Code name", os.CodeName }, - { "Kernel", os.Kernel }, - { "Architecture", os.Architecture }, - { "Code page", os.CodePage }, - { "Logo file", os.LogoFile }, - { "Build", os.Build }, - { "Service pack", os.ServicePack }, - { "UEFI", os.IsUefi } + public static class ExcelConverter + { + public static byte[] Convert(Report report, ILanguageInfoProvider languageInfoProvider) + { + ExcelPackage.LicenseContext = LicenseContext.NonCommercial; + + using ExcelPackage excelPackage = new(); + + var reportSheet = excelPackage.Workbook.Worksheets.Add($"Report"); + FillReportSheet(reportSheet, report); + + var resultSheet = excelPackage.Workbook.Worksheets.Add($"Results"); + FillResultsSheet(resultSheet, report.Results, languageInfoProvider); + + return excelPackage.GetAsByteArray(); + } + + private static void FillReportSheet(ExcelWorksheet sheet, Report report) + { + int rowNumber = 1; + + AddGeneralSection(sheet, ref rowNumber, report); + AddCPUSection(sheet, ref rowNumber, report.CPU); + AddOperatingSystemSection(sheet, ref rowNumber, report.OperatingSystem); + AddSystemSection(sheet, ref rowNumber, report.System); + AddDockerSection(sheet, ref rowNumber, report.DockerInfo); + + sheet.Column(1).Style.Font.Bold = true; + sheet.Column(2).Style.HorizontalAlignment = ExcelHorizontalAlignment.Left; + sheet.Columns.Style.VerticalAlignment = ExcelVerticalAlignment.Top; + sheet.Columns.AutoFit(); + } + + private static void AddGeneralSection(ExcelWorksheet sheet, ref int rowNumber, Report report) + { + AddExpandableSection(sheet, ref rowNumber, "General", (sheet, rowNumber) => + { + int sectionTop = rowNumber; + if (AddValue(sheet, ref rowNumber, "Id", report.Id)) + { + sheet.Rows[sectionTop, rowNumber - 1].Hidden = true; + } + AddValue(sheet, ref rowNumber, "User", report.User); + AddValue(sheet, ref rowNumber, "Created at", report.Date, format: "yyyy-mm-dd HH:MM:SS"); + return rowNumber; + }); + } + + private static void AddCPUSection(ExcelWorksheet sheet, ref int rowNumber, CPUInfo cpu) + { + AddExpandableSection(sheet, ref rowNumber, "CPU", (sheet, rowNumber) => + { + AddValues(sheet, ref rowNumber, new() + { + { "Manufacturer", cpu.Manufacturer }, + { "Raspberry processor", cpu.RaspberryProcessor }, + { "Brand", cpu.Brand }, + { "Vendor", cpu.Vendor }, + { "Family", cpu.Family }, + { "Model", cpu.Model }, + { "Stepping", cpu.Stepping }, + { "Revision", cpu.Revision }, + { "# Cores", cpu.Cores }, + { "# Efficiency cores", cpu.EfficiencyCores }, + { "# Performance cores", cpu.PerformanceCores }, + { "# Physical cores", cpu.PhysicalCores }, + { "# Processors", cpu.Processors }, + { "Speed", cpu.Speed }, + { "Minimum speed", cpu.MinimumSpeed }, + { "Maximum speed", cpu.MaximumSpeed }, + { "Voltage", cpu.Voltage }, + { "Governor", cpu.Governor }, + { "Socket", cpu.Socket } + }); + if (cpu.FlagValues != null) + AddValue(sheet, ref rowNumber, "Flags", string.Join(", ", cpu.FlagValues.OrderBy(f => f)), wordWrap: true); + AddValue(sheet, ref rowNumber, "Virtualization", cpu.Virtualization); + if (cpu.Cache != null && cpu.Cache.Count > 0) + { + AddValue(sheet, ref rowNumber, "Cache", force: true); + foreach (var cacheLine in cpu.Cache) + AddValue(sheet, ref rowNumber, $"- {cacheLine.Key}", cacheLine.Value); + } + return rowNumber; + }); + } + + private static void AddOperatingSystemSection(ExcelWorksheet sheet, ref int rowNumber, OperatingSystemInfo os) + { + AddExpandableValuesSection(sheet, ref rowNumber, "Operating System", new() + { + { "Platform", os.Platform }, + { "Distribution", os.Distribution }, + { "Release", os.Release }, + { "Code name", os.CodeName }, + { "Kernel", os.Kernel }, + { "Architecture", os.Architecture }, + { "Code page", os.CodePage }, + { "Logo file", os.LogoFile }, + { "Build", os.Build }, + { "Service pack", os.ServicePack }, + { "UEFI", os.IsUefi } }); } private static void AddSystemSection(ExcelWorksheet sheet, ref int rowNumber, SystemInfo system) - { + { AddExpandableValuesSection(sheet, ref rowNumber, "System", new() { - { "Manufacturer", system.Manufacturer }, - { "Raspberry manufacturer", system.RaspberryManufacturer }, - { "SKU", system.SKU }, - { "Virtual", system.IsVirtual }, - { "Model", system.Model }, - { "Version", system.Version }, - { "Raspberry type", system.RaspberryType }, - { "Raspberry revision", system.RaspberryRevision } + { "Manufacturer", system.Manufacturer }, + { "Raspberry manufacturer", system.RaspberryManufacturer }, + { "SKU", system.SKU }, + { "Virtual", system.IsVirtual }, + { "Model", system.Model }, + { "Version", system.Version }, + { "Raspberry type", system.RaspberryType }, + { "Raspberry revision", system.RaspberryRevision } }); } private static void AddDockerSection(ExcelWorksheet sheet, ref int rowNumber, DockerInfo docker) - { - AddExpandableValuesSection(sheet, ref rowNumber, "Docker", new() - { - { "Kernel version", docker.KernelVersion }, - { "Operating system", docker.OperatingSystem }, - { "OS version", docker.OSVersion }, - { "OS type", docker.OSType }, - { "Architecture", docker.Architecture }, - { "# CPUs", docker.CPUCount }, - { "Total memory", docker.TotalMemory }, - { "Server version", docker.ServerVersion } - }); - } - - private static void AddExpandableValuesSection(ExcelWorksheet sheet, ref int rowNumber, string title, List> entries) - { - AddExpandableSection(sheet, ref rowNumber, title, (sheet, rowNumber) => - { - AddValues(sheet, ref rowNumber, entries); - return rowNumber; - }); - } - - private static void AddExpandableSection(ExcelWorksheet sheet, ref int rowNumber, string title, Func AddEntries) - { + { + AddExpandableValuesSection(sheet, ref rowNumber, "Docker", new() + { + { "Kernel version", docker.KernelVersion }, + { "Operating system", docker.OperatingSystem }, + { "OS version", docker.OSVersion }, + { "OS type", docker.OSType }, + { "Architecture", docker.Architecture }, + { "# CPUs", docker.CPUCount }, + { "Total memory", docker.TotalMemory }, + { "Server version", docker.ServerVersion } + }); + } + + private static void AddExpandableValuesSection(ExcelWorksheet sheet, ref int rowNumber, string title, List> entries) + { + AddExpandableSection(sheet, ref rowNumber, title, (sheet, rowNumber) => + { + AddValues(sheet, ref rowNumber, entries); + return rowNumber; + }); + } + + private static void AddExpandableSection(ExcelWorksheet sheet, ref int rowNumber, string title, Func AddEntries) + { var cell = sheet.Cells[rowNumber, 1]; cell.Value = title; @@ -162,13 +162,13 @@ private static void AddExpandableSection(ExcelWorksheet sheet, ref int rowNumber cell.Style.Font.UnderLine = true; rowNumber++; - - int sectionTop = rowNumber; - rowNumber = AddEntries(sheet, rowNumber); - int sectionBottom = rowNumber - 1; - - rowNumber++; + int sectionTop = rowNumber; + rowNumber = AddEntries(sheet, rowNumber); + + int sectionBottom = rowNumber - 1; + + rowNumber++; if (sectionBottom <= sectionTop) return; @@ -178,90 +178,90 @@ private static void AddExpandableSection(ExcelWorksheet sheet, ref int rowNumber rowRange.Collapsed = false; } - private static bool AddValues(ExcelWorksheet sheet, ref int rowNumber, List> entries) - { - bool result = false; - foreach (var entry in entries) - result |= AddValue(sheet, ref rowNumber, entry.Key, entry.Value); - return result; - } + private static bool AddValues(ExcelWorksheet sheet, ref int rowNumber, List> entries) + { + bool result = false; + foreach (var entry in entries) + result |= AddValue(sheet, ref rowNumber, entry.Key, entry.Value); + return result; + } - private static bool AddValue(ExcelWorksheet sheet, ref int rowNumber, string label, object value = null, bool force = false, string format = null, bool wordWrap = false) + private static bool AddValue(ExcelWorksheet sheet, ref int rowNumber, string label, object value = null, bool force = false, string format = null, bool wordWrap = false) { - if (!force && ((value is string stringValue && string.IsNullOrEmpty(stringValue)) || (value is not string && value == null))) - return false; + if (!force && ((value is string stringValue && string.IsNullOrEmpty(stringValue)) || (value is not string && value == null))) + return false; - sheet.Cells[rowNumber, 1].Value = label + ':'; - var valueCell = sheet.Cells[rowNumber, 2]; - valueCell.Value = value; - if (format != null) + sheet.Cells[rowNumber, 1].Value = label + ':'; + var valueCell = sheet.Cells[rowNumber, 2]; + valueCell.Value = value; + if (format != null) valueCell.Style.Numberformat.Format = format; - if (wordWrap) - valueCell.Style.WrapText = true; + if (wordWrap) + valueCell.Style.WrapText = true; - rowNumber++; - return true; + rowNumber++; + return true; } - private static void FillResultsSheet(ExcelWorksheet sheet, IEnumerable results, ILanguageInfoProvider languageInfoProvider) + private static void FillResultsSheet(ExcelWorksheet sheet, IEnumerable results, ILanguageInfoProvider languageInfoProvider) { - sheet.Cells.LoadFromCollection(results); - int lastRow = sheet.Dimension.Rows - 1; - for (int i = 2; i <= lastRow; i++) - { - var languageInfo = languageInfoProvider.GetLanguageInfo(sheet.Cells[i, Result.LanguageColumnIndex].Text); - - var cell = sheet.Cells[i, Result.LanguageColumnIndex]; - cell.Value = languageInfo.Name; - ExcelFont font; - - if (!string.IsNullOrEmpty(languageInfo.URL)) - { - cell.Hyperlink = new Uri(languageInfo.URL); - font = cell.Style.Font; - font.Color.SetColor(Color.Blue); - font.UnderLine = true; - } - - var uriText = sheet.Cells[i, Result.SolutionUriColumnIndex].Text; - if (!string.IsNullOrEmpty(uriText)) - { - cell = sheet.Cells[i, Result.SolutionColumnIndex]; - cell.Hyperlink = new Uri(uriText); - font = cell.Style.Font; - font.Color.SetColor(Color.Blue); - font.UnderLine = true; - } - - bool allgreen = true; - - cell = sheet.Cells[i, Result.AlgorithmColumnIndex]; - if (cell.Value as string == "base") - cell.Style.Font.Color.SetColor(Color.Green); - else - allgreen = false; - - cell = sheet.Cells[i, Result.IsFaithfulColumnIndex]; - if (cell.Value as bool? ?? false) - cell.Style.Font.Color.SetColor(Color.Green); - else - allgreen = false; - - cell = sheet.Cells[i, Result.BitsColumnIndex]; - if (cell.Value is int && (int)cell.Value == 1) - cell.Style.Font.Color.SetColor(Color.Green); - else - allgreen = false; - - if (allgreen) - sheet.Cells[i, Result.LabelColumnIndex].Style.Font.Color.SetColor(Color.Green); - } - - sheet.Cells[2, Result.SolutionColumnIndex, lastRow, Result.SolutionColumnIndex].Style.HorizontalAlignment = ExcelHorizontalAlignment.Right; - sheet.Column(Result.SolutionUriColumnIndex).Hidden = true; - sheet.Cells[2, Result.IsMultiThreadedColumnIndex, lastRow, Result.IsMultiThreadedColumnIndex].Style.Font.Color.SetColor(Color.Green); - sheet.Columns.AutoFit(); - } + sheet.Cells.LoadFromCollection(results); + int lastRow = sheet.Dimension.Rows - 1; + for (int i = 2; i <= lastRow; i++) + { + var languageInfo = languageInfoProvider.GetLanguageInfo(sheet.Cells[i, Result.LanguageColumnIndex].Text); + + var cell = sheet.Cells[i, Result.LanguageColumnIndex]; + cell.Value = languageInfo.Name; + ExcelFont font; + + if (!string.IsNullOrEmpty(languageInfo.URL)) + { + cell.Hyperlink = new Uri(languageInfo.URL); + font = cell.Style.Font; + font.Color.SetColor(Color.Blue); + font.UnderLine = true; + } + + var uriText = sheet.Cells[i, Result.SolutionUriColumnIndex].Text; + if (!string.IsNullOrEmpty(uriText)) + { + cell = sheet.Cells[i, Result.SolutionColumnIndex]; + cell.Hyperlink = new Uri(uriText); + font = cell.Style.Font; + font.Color.SetColor(Color.Blue); + font.UnderLine = true; + } + + bool allgreen = true; + + cell = sheet.Cells[i, Result.AlgorithmColumnIndex]; + if (cell.Value as string == "base") + cell.Style.Font.Color.SetColor(Color.Green); + else + allgreen = false; + + cell = sheet.Cells[i, Result.IsFaithfulColumnIndex]; + if (cell.Value as bool? ?? false) + cell.Style.Font.Color.SetColor(Color.Green); + else + allgreen = false; + + cell = sheet.Cells[i, Result.BitsColumnIndex]; + if (cell.Value is int value && value == 1) + cell.Style.Font.Color.SetColor(Color.Green); + else + allgreen = false; + + if (allgreen) + sheet.Cells[i, Result.LabelColumnIndex].Style.Font.Color.SetColor(Color.Green); + } + + sheet.Cells[2, Result.SolutionColumnIndex, lastRow, Result.SolutionColumnIndex].Style.HorizontalAlignment = ExcelHorizontalAlignment.Right; + sheet.Column(Result.SolutionUriColumnIndex).Hidden = true; + sheet.Cells[2, Result.IsMultiThreadedColumnIndex, lastRow, Result.IsMultiThreadedColumnIndex].Style.Font.Color.SetColor(Color.Green); + sheet.Columns.AutoFit(); + } public static void Add(this List> pairList, T1 key, T2 value) { diff --git a/src/Frontend/ReportExporters/JsonConverter.cs b/src/Frontend/ReportExporters/JsonConverter.cs index 3285639..7efa8d3 100644 --- a/src/Frontend/ReportExporters/JsonConverter.cs +++ b/src/Frontend/ReportExporters/JsonConverter.cs @@ -5,26 +5,26 @@ namespace PrimeView.Frontend.ReportExporters { - public static class JsonConverter - { - public static byte[] Convert(Report report) - { - string jsonValue; - try - { - jsonValue = JsonSerializer.Serialize(report, options: new() - { - DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, - WriteIndented = true, - PropertyNamingPolicy = JsonNamingPolicy.CamelCase - }); - } - catch - { - return null; - } + public static class JsonConverter + { + public static byte[] Convert(Report report) + { + string jsonValue; + try + { + jsonValue = JsonSerializer.Serialize(report, options: new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull, + WriteIndented = true, + PropertyNamingPolicy = JsonNamingPolicy.CamelCase + }); + } + catch + { + return null; + } - return Encoding.UTF8.GetBytes(jsonValue); - } - } + return Encoding.UTF8.GetBytes(jsonValue); + } + } } diff --git a/src/Frontend/Sorting/SortedTablePage.razor.cs b/src/Frontend/Sorting/SortedTablePage.razor.cs index 0ebb34f..e9b48df 100644 --- a/src/Frontend/Sorting/SortedTablePage.razor.cs +++ b/src/Frontend/Sorting/SortedTablePage.razor.cs @@ -7,84 +7,84 @@ namespace PrimeView.Frontend.Sorting { - public partial class SortedTablePage : ComponentBase - { - [Inject] - public NavigationManager NavigationManager { get; set; } + public partial class SortedTablePage : ComponentBase + { + [Inject] + public NavigationManager NavigationManager { get; set; } - [Inject] - public ISyncLocalStorageService LocalStorage { get; set; } + [Inject] + public ISyncLocalStorageService LocalStorage { get; set; } - [Inject] - public IJSInProcessRuntime JSRuntime { get; set; } + [Inject] + public IJSInProcessRuntime JSRuntime { get; set; } - [QueryStringParameter("sc")] - public string SortColumn { get; set; } = string.Empty; + [QueryStringParameter("sc")] + public string SortColumn { get; set; } = string.Empty; - [QueryStringParameter("sd")] - public bool SortDescending { get; set; } = false; + [QueryStringParameter("sd")] + public bool SortDescending { get; set; } = false; - protected Table sortedTable; + protected Table sortedTable; - private bool processTableSortingChange = false; + private bool processTableSortingChange = false; - public override Task SetParametersAsync(ParameterView parameters) - { - this.SetParametersFromQueryString(NavigationManager, LocalStorage); + public override Task SetParametersAsync(ParameterView parameters) + { + this.SetParametersFromQueryString(NavigationManager, LocalStorage); - return base.SetParametersAsync(parameters); - } + return base.SetParametersAsync(parameters); + } - protected override async Task OnAfterRenderAsync(bool firstRender) - { - (string sortColumn, bool sortDescending) = this.sortedTable.GetSortParameterValues(); + protected override async Task OnAfterRenderAsync(bool firstRender) + { + (string sortColumn, bool sortDescending) = this.sortedTable.GetSortParameterValues(); - if (!this.processTableSortingChange && (!SortColumn.EqualsIgnoreCaseOrNull(sortColumn) || SortDescending != sortDescending)) - { - if (this.sortedTable.SetSortParameterValues(SortColumn, SortDescending)) - await this.sortedTable.UpdateAsync(); - } + if (!this.processTableSortingChange && (!SortColumn.EqualsIgnoreCaseOrNull(sortColumn) || SortDescending != sortDescending)) + { + if (this.sortedTable.SetSortParameterValues(SortColumn, SortDescending)) + await this.sortedTable.UpdateAsync(); + } - UpdateQueryString(); + UpdateQueryString(); - await base.OnAfterRenderAsync(firstRender); - } + await base.OnAfterRenderAsync(firstRender); + } - protected void FlagTableSortingChange() - { - this.processTableSortingChange = true; - } + protected void FlagTableSortingChange() + { + this.processTableSortingChange = true; + } - protected virtual void OnTableRefreshStart() - { - if (!this.processTableSortingChange) - return; + protected virtual void OnTableRefreshStart() + { + if (!this.processTableSortingChange) + return; - (string sortColumn, bool sortDescending) = this.sortedTable.GetSortParameterValues(); + (string sortColumn, bool sortDescending) = this.sortedTable.GetSortParameterValues(); - this.processTableSortingChange = false; + this.processTableSortingChange = false; - bool queryStringUpdateRequired = false; + bool queryStringUpdateRequired = false; - if (!sortColumn.EqualsIgnoreCaseOrNull(SortColumn)) - { - SortColumn = sortColumn; - queryStringUpdateRequired = true; - } + if (!sortColumn.EqualsIgnoreCaseOrNull(SortColumn)) + { + SortColumn = sortColumn; + queryStringUpdateRequired = true; + } - if (sortDescending != SortDescending) - { - SortDescending = sortDescending; - queryStringUpdateRequired = true; - } + if (sortDescending != SortDescending) + { + SortDescending = sortDescending; + queryStringUpdateRequired = true; + } - if (queryStringUpdateRequired) - UpdateQueryString(); - } + if (queryStringUpdateRequired) + UpdateQueryString(); + } - protected virtual void UpdateQueryString() - { - this.UpdateQueryString(NavigationManager, LocalStorage, JSRuntime); - } - } + protected virtual void UpdateQueryString() + { + this.UpdateQueryString(NavigationManager, LocalStorage, JSRuntime); + } + } } diff --git a/src/Frontend/Sorting/SortingExtensions.cs b/src/Frontend/Sorting/SortingExtensions.cs index 7bb859f..3702086 100644 --- a/src/Frontend/Sorting/SortingExtensions.cs +++ b/src/Frontend/Sorting/SortingExtensions.cs @@ -4,50 +4,50 @@ namespace PrimeView.Frontend.Sorting { - public static class SortingExtensions - { - public static (string sortColumn, bool sortDescending) GetSortParameterValues(this Table table) - { - if (table == null) - return (null, false); - - foreach (var column in table.Columns) - { - if (column.Field == null) - continue; - - if (column.SortColumn) - return (sortColumn: column.Field.GetPropertyParameterName(), sortDescending: column.SortDescending); - } - - return (null, false); - } - - public static bool SetSortParameterValues(this Table table, string sortColumn, bool sortDescending) - { - if (table == null || sortColumn == null) - return false; - - foreach (var column in table.Columns) - { - if (column.Field == null || !column.Sortable) - continue; - - if (column.Field.GetPropertyParameterName().EqualsIgnoreCaseOrNull(sortColumn)) - { - column.SortColumn = true; - column.SortDescending = sortDescending; - - return true; - } - } - - return false; - } - - public static bool EqualsIgnoreCaseOrNull(this string x, string y) - { - return (x == null && y == null) || (x != null && y != null && x.Equals(y, StringComparison.OrdinalIgnoreCase)); - } - } + public static class SortingExtensions + { + public static (string sortColumn, bool sortDescending) GetSortParameterValues(this Table table) + { + if (table == null) + return (null, false); + + foreach (var column in table.Columns) + { + if (column.Field == null) + continue; + + if (column.SortColumn) + return (sortColumn: column.Field.GetPropertyParameterName(), sortDescending: column.SortDescending); + } + + return (null, false); + } + + public static bool SetSortParameterValues(this Table table, string sortColumn, bool sortDescending) + { + if (table == null || sortColumn == null) + return false; + + foreach (var column in table.Columns) + { + if (column.Field == null || !column.Sortable) + continue; + + if (column.Field.GetPropertyParameterName().EqualsIgnoreCaseOrNull(sortColumn)) + { + column.SortColumn = true; + column.SortDescending = sortDescending; + + return true; + } + } + + return false; + } + + public static bool EqualsIgnoreCaseOrNull(this string x, string y) + { + return (x == null && y == null) || (x != null && y != null && x.Equals(y, StringComparison.OrdinalIgnoreCase)); + } + } } diff --git a/src/Frontend/Tools/Constants.cs b/src/Frontend/Tools/Constants.cs index cb42333..a254093 100644 --- a/src/Frontend/Tools/Constants.cs +++ b/src/Frontend/Tools/Constants.cs @@ -1,22 +1,22 @@ namespace PrimeView.Frontend.Tools { - public static class Constants - { - public const string ReportCount = nameof(ReportCount); - public const string ActiveReader = nameof(ActiveReader); - public const string SolutionUrlTemplate = nameof(SolutionUrlTemplate); - public const string Readers = nameof(Readers); - public const string JsonFileReader = nameof(JsonFileReader); - public const string RestAPIReader = nameof(RestAPIReader); + public static class Constants + { + public const string ReportCount = nameof(ReportCount); + public const string ActiveReader = nameof(ActiveReader); + public const string SolutionUrlTemplate = nameof(SolutionUrlTemplate); + public const string Readers = nameof(Readers); + public const string JsonFileReader = nameof(JsonFileReader); + public const string RestAPIReader = nameof(RestAPIReader); - public const string SinglethreadedTag = "st"; - public const string MultithreadedTag = "mt"; - public const string BaseTag = "ba"; - public const string WheelTag = "wh"; - public const string OtherTag = "ot"; - public const string FaithfulTag = "ff"; - public const string UnfaithfulTag = "uf"; - public const string UnknownTag = "uk"; - public const string OneTag = "on"; - } + public const string SinglethreadedTag = "st"; + public const string MultithreadedTag = "mt"; + public const string BaseTag = "ba"; + public const string WheelTag = "wh"; + public const string OtherTag = "ot"; + public const string FaithfulTag = "ff"; + public const string UnfaithfulTag = "uf"; + public const string UnknownTag = "uk"; + public const string OneTag = "on"; + } } diff --git a/src/Frontend/Tools/ILanguageInfoProvider.cs b/src/Frontend/Tools/ILanguageInfoProvider.cs index 4a8d9ed..11cf9d6 100644 --- a/src/Frontend/Tools/ILanguageInfoProvider.cs +++ b/src/Frontend/Tools/ILanguageInfoProvider.cs @@ -1,7 +1,7 @@ namespace PrimeView.Frontend.Tools { - public interface ILanguageInfoProvider - { - public LanguageInfo GetLanguageInfo(string language); - } + public interface ILanguageInfoProvider + { + public LanguageInfo GetLanguageInfo(string language); + } } diff --git a/src/Frontend/Tools/LanguageInfo.cs b/src/Frontend/Tools/LanguageInfo.cs index 2a404a4..ac7c363 100644 --- a/src/Frontend/Tools/LanguageInfo.cs +++ b/src/Frontend/Tools/LanguageInfo.cs @@ -3,24 +3,24 @@ namespace PrimeView.Frontend.Tools { - public class LanguageInfo - { - public string Key { get; set; } - public string Name { get; set; } - public string URL { get; set; } - public string Tag { get; set; } + public class LanguageInfo + { + public string Key { get; set; } + public string Name { get; set; } + public string URL { get; set; } + public string Tag { get; set; } - public class KeyEqualityComparer : IEqualityComparer - { - public bool Equals(LanguageInfo x, LanguageInfo y) - { - return (x == null && y == null) || (x != null && y != null && x.Key == y.Key); - } + public class KeyEqualityComparer : IEqualityComparer + { + public bool Equals(LanguageInfo x, LanguageInfo y) + { + return (x == null && y == null) || (x != null && y != null && x.Key == y.Key); + } - public int GetHashCode([DisallowNull] LanguageInfo obj) - { - return obj.Key.GetHashCode(); - } - } - } + public int GetHashCode([DisallowNull] LanguageInfo obj) + { + return obj.Key.GetHashCode(); + } + } + } } diff --git a/src/Frontend/wwwroot/data/langmap.json b/src/Frontend/wwwroot/data/langmap.json index 3c22ba0..c79d93e 100644 --- a/src/Frontend/wwwroot/data/langmap.json +++ b/src/Frontend/wwwroot/data/langmap.json @@ -277,6 +277,10 @@ "name": "Prolog", "url": "https://en.wikipedia.org/wiki/Prolog" }, + "pyret": { + "name": "Pyret", + "url": "https://pyret.org/index.html" + }, "python": { "name": "Python", "url": "https://www.python.org/" diff --git a/src/JsonFileReader/Constants.cs b/src/JsonFileReader/Constants.cs index 875322e..b831d34 100644 --- a/src/JsonFileReader/Constants.cs +++ b/src/JsonFileReader/Constants.cs @@ -1,9 +1,9 @@ namespace PrimeView.JsonFileReader { - static class Constants - { - public const string BaseURI = nameof(BaseURI); - public const string Index = nameof(Index); - public const string IsS3Bucket = nameof(IsS3Bucket); - } + static class Constants + { + public const string BaseURI = nameof(BaseURI); + public const string Index = nameof(Index); + public const string IsS3Bucket = nameof(IsS3Bucket); + } } diff --git a/src/JsonFileReader/ExtensionMethods.cs b/src/JsonFileReader/ExtensionMethods.cs index ff1b215..b663368 100644 --- a/src/JsonFileReader/ExtensionMethods.cs +++ b/src/JsonFileReader/ExtensionMethods.cs @@ -6,131 +6,131 @@ namespace PrimeView.JsonFileReader { - public static class ExtensionMethods - { - private static readonly JsonSerializerOptions serializerOptions = new() - { - PropertyNameCaseInsensitive = true, - AllowTrailingCommas = true - }; - - public static int GetStableHashCode(this string str) - { - unchecked - { - int hash1 = 5381; - int hash2 = hash1; - - for (int i = 0; i < str.Length && str[i] != '\0'; i += 2) - { - hash1 = ((hash1 << 5) + hash1) ^ str[i]; - if (i == str.Length - 1 || str[i + 1] == '\0') - { - break; - } - - hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; - } - - return hash1 + (hash2 * 1566083941); - } - } - - public static IServiceCollection AddJsonFileReportReader(this IServiceCollection serviceCollection, string baseAddress, IConfiguration configuration) - { - return serviceCollection.AddScoped(sp => new ReportReader(baseAddress, configuration)); - } - - public static T? Get(this JsonElement element) - { - try - { - return JsonSerializer.Deserialize(element.GetRawText(), serializerOptions); - } - catch (Exception ex) - { - Console.WriteLine(ex); - } - - return default; - } - - public static T? Get(this JsonElement element, string propertyName) where T : class - { - return GetElement(element, propertyName)?.Get(); - } - - public static T? Get(this JsonElement? element, string propertyName) where T : class - { - return element.HasValue ? Get(element.Value, propertyName) : null; - } - - public static int? GetInt32(this JsonElement? element, string propertyName) - { - return element.HasValue ? GetInt32(element.Value, propertyName) : null; - } - - public static int? GetInt32(this JsonElement element, string propertyName) - { - var childElement = GetElement(element, propertyName); - - return childElement.HasValue && childElement.Value.TryGetInt32(out int value) ? value : null; - } - - public static long? GetInt64(this JsonElement? element, string propertyName) - { - return element.HasValue ? GetInt64(element.Value, propertyName) : null; - } - - public static long? GetInt64(this JsonElement element, string propertyName) - { - var childElement = GetElement(element, propertyName); - - return childElement.HasValue && childElement.Value.TryGetInt64(out long value) ? value : null; - } - - public static double? GetDouble(this JsonElement? element, string propertyName) - { - return element.HasValue ? GetDouble(element.Value, propertyName) : null; - } - - public static double? GetDouble(this JsonElement element, string propertyName) - { - var childElement = GetElement(element, propertyName); - - return childElement.HasValue && childElement.Value.TryGetDouble(out double value) ? value : null; - } - - public static string? GetString(this JsonElement? element, string propertyName) - { - return element.HasValue ? GetString(element.Value, propertyName) : null; - } - - public static string? GetString(this JsonElement element, string propertyName) - { - return GetElement(element, propertyName)?.GetString(); - } - - public static DateTime? GetDateFromUnixTimeSeconds(this JsonElement? element, string propertyName) - { - return element.HasValue ? GetDateFromUnixTimeSeconds(element.Value, propertyName) : null; - } - - public static DateTime? GetDateFromUnixTimeSeconds(this JsonElement element, string propertyName) - { - int? seconds = GetInt32(element, propertyName); - - return seconds.HasValue ? DateTimeOffset.FromUnixTimeSeconds(seconds.Value).DateTime : null; - } - - public static JsonElement? GetElement(this JsonElement element, string propertyName) - { - return element.TryGetProperty(propertyName, out var childElement) ? childElement : null; - } - - public static JsonElement? GetElement(this JsonElement? element, string propertyName) - { - return element.HasValue ? GetElement(element.Value, propertyName) : null; - } - } + public static class ExtensionMethods + { + private static readonly JsonSerializerOptions serializerOptions = new() + { + PropertyNameCaseInsensitive = true, + AllowTrailingCommas = true + }; + + public static int GetStableHashCode(this string str) + { + unchecked + { + int hash1 = 5381; + int hash2 = hash1; + + for (int i = 0; i < str.Length && str[i] != '\0'; i += 2) + { + hash1 = ((hash1 << 5) + hash1) ^ str[i]; + if (i == str.Length - 1 || str[i + 1] == '\0') + { + break; + } + + hash2 = ((hash2 << 5) + hash2) ^ str[i + 1]; + } + + return hash1 + (hash2 * 1566083941); + } + } + + public static IServiceCollection AddJsonFileReportReader(this IServiceCollection serviceCollection, string baseAddress, IConfiguration configuration) + { + return serviceCollection.AddScoped(sp => new ReportReader(baseAddress, configuration)); + } + + public static T? Get(this JsonElement element) + { + try + { + return JsonSerializer.Deserialize(element.GetRawText(), serializerOptions); + } + catch (Exception ex) + { + Console.WriteLine(ex); + } + + return default; + } + + public static T? Get(this JsonElement element, string propertyName) where T : class + { + return GetElement(element, propertyName)?.Get(); + } + + public static T? Get(this JsonElement? element, string propertyName) where T : class + { + return element.HasValue ? Get(element.Value, propertyName) : null; + } + + public static int? GetInt32(this JsonElement? element, string propertyName) + { + return element.HasValue ? GetInt32(element.Value, propertyName) : null; + } + + public static int? GetInt32(this JsonElement element, string propertyName) + { + var childElement = GetElement(element, propertyName); + + return childElement.HasValue && childElement.Value.TryGetInt32(out int value) ? value : null; + } + + public static long? GetInt64(this JsonElement? element, string propertyName) + { + return element.HasValue ? GetInt64(element.Value, propertyName) : null; + } + + public static long? GetInt64(this JsonElement element, string propertyName) + { + var childElement = GetElement(element, propertyName); + + return childElement.HasValue && childElement.Value.TryGetInt64(out long value) ? value : null; + } + + public static double? GetDouble(this JsonElement? element, string propertyName) + { + return element.HasValue ? GetDouble(element.Value, propertyName) : null; + } + + public static double? GetDouble(this JsonElement element, string propertyName) + { + var childElement = GetElement(element, propertyName); + + return childElement.HasValue && childElement.Value.TryGetDouble(out double value) ? value : null; + } + + public static string? GetString(this JsonElement? element, string propertyName) + { + return element.HasValue ? GetString(element.Value, propertyName) : null; + } + + public static string? GetString(this JsonElement element, string propertyName) + { + return GetElement(element, propertyName)?.GetString(); + } + + public static DateTime? GetDateFromUnixTimeSeconds(this JsonElement? element, string propertyName) + { + return element.HasValue ? GetDateFromUnixTimeSeconds(element.Value, propertyName) : null; + } + + public static DateTime? GetDateFromUnixTimeSeconds(this JsonElement element, string propertyName) + { + int? seconds = GetInt32(element, propertyName); + + return seconds.HasValue ? DateTimeOffset.FromUnixTimeSeconds(seconds.Value).DateTime : null; + } + + public static JsonElement? GetElement(this JsonElement element, string propertyName) + { + return element.TryGetProperty(propertyName, out var childElement) ? childElement : null; + } + + public static JsonElement? GetElement(this JsonElement? element, string propertyName) + { + return element.HasValue ? GetElement(element.Value, propertyName) : null; + } + } } diff --git a/src/JsonFileReader/ReportReader.cs b/src/JsonFileReader/ReportReader.cs index aa8e55e..8d09186 100644 --- a/src/JsonFileReader/ReportReader.cs +++ b/src/JsonFileReader/ReportReader.cs @@ -9,231 +9,231 @@ namespace PrimeView.JsonFileReader { - public class ReportReader : IReportReader - { - private List? summaries; - private Dictionary? reportMap; - private readonly HttpClient httpClient; - private readonly string? indexFileName; - private readonly bool isS3Bucket; - private bool haveJsonFilesLoaded = false; - private bool reachedMaxFileCount = false; - private int totalReports = 0; - - public ReportReader(string baseAddress, IConfiguration configuration) - { - this.httpClient = new HttpClient { BaseAddress = new Uri(configuration.GetValue(Constants.BaseURI, baseAddress) ?? baseAddress) }; - this.indexFileName = configuration.GetValue(Constants.Index, null); - this.isS3Bucket = configuration.GetValue(Constants.IsS3Bucket, false); - } - - private async Task LoadReportJsonFile(string fileName) - { - if (string.IsNullOrEmpty(fileName)) - return null; - - if (haveJsonFilesLoaded && reportMap!.TryGetValue(fileName, out Report? report)) - return report; - - string reportJson; - - try - { - reportJson = await this.httpClient.GetStringAsync(fileName); - } - catch - { - return null; - } - - var reportElement = JsonDocument.Parse(reportJson).RootElement; - return ParseReportElement(reportJson, reportElement, fileName); - } - - private async Task LoadReportJsonFiles(int maxFileCount) - { - if (this.haveJsonFilesLoaded && (!this.reachedMaxFileCount || maxFileCount <= this.reportMap!.Count)) - return; - - string[]? reportFileNames = null; - - if (this.isS3Bucket) - reportFileNames = await S3BucketIndexReader.GetFileNames(this.httpClient); - - else if (this.indexFileName != null) - { - try - { - reportFileNames = JsonSerializer.Deserialize(await this.httpClient.GetStringAsync(this.indexFileName)); - } - catch { } - } - - this.summaries = []; - this.reportMap = []; - this.reachedMaxFileCount = false; - this.totalReports = reportFileNames?.Length ?? maxFileCount; - - Dictionary> stringReaderMap = []; - - for (int fileIndex = 0; fileIndex != reportFileNames?.Length; fileIndex++) - { - string fileName = reportFileNames != null ? reportFileNames[fileIndex] : $"data/report{fileIndex + 1}.json"; - - stringReaderMap[fileName] = this.httpClient.GetStringAsync(fileName); - - if (--maxFileCount <= 0) - { - this.reachedMaxFileCount = true; - break; - } - } - - foreach (var item in stringReaderMap) - { - string reportJson; - string fileName = item.Key; - - try - { - reportJson = await item.Value; - } - catch (HttpRequestException) - { - // break out of the loop on error if we're not reading a list of files we retrieved from the index file... - if (reportFileNames == null) - break; - - // ...otherwise try reading the next file - else - continue; - } - - try - { - var reportElement = JsonDocument.Parse(reportJson).RootElement; - Report report = ParseReportElement(reportJson, reportElement, fileName); - - this.reportMap[fileName] = report; - this.summaries.Add(ExtractSummary(report)); - } - catch - { - Console.WriteLine($"Report parsing of file {fileName} failed"); - } - } - - this.haveJsonFilesLoaded = true; - } - - private static ReportSummary ExtractSummary(Report report) - { - return new() - { - Id = report.Id, - Architecture = report.OperatingSystem?.Architecture, - CpuBrand = report.CPU?.Brand, - CpuCores = report.CPU?.Cores, - CpuProcessors = report.CPU?.Processors, - CpuVendor = report.CPU?.Vendor, - Date = report.Date, - DockerArchitecture = report.DockerInfo?.Architecture, - IsSystemVirtual = report.System?.IsVirtual, - OsPlatform = report.OperatingSystem?.Platform, - OsDistro = report.OperatingSystem?.Distribution, - OsRelease = report.OperatingSystem?.Release, - ResultCount = report.Results?.Length ?? 0, - User = report.User - }; - } - - private static Report ParseReportElement(string json, JsonElement element, string? id = null) - { - var machineElement = element.GetElement("machine"); - var metadataElement = element.GetElement("metadata"); - - Report report = new() - { - Id = metadataElement.GetString("id") ?? id ?? json.GetStableHashCode().ToString(), - Date = metadataElement.GetDateFromUnixTimeSeconds("date"), - User = metadataElement.GetString("user"), - CPU = machineElement.Get("cpu"), - OperatingSystem = machineElement.Get("os"), - System = machineElement.Get("system"), - DockerInfo = machineElement.Get("docker") - }; - - var resultsElement = element.GetElement("results"); - - List results = []; - - if (resultsElement.HasValue && resultsElement.Value.ValueKind == JsonValueKind.Array) - { - foreach (var resultElement in resultsElement.Value.EnumerateArray()) - { - results.Add(ParseResultElement(resultElement)); - } - } - - report.Results = [..results]; - - return report; - } - - private static Result ParseResultElement(JsonElement element) - { - var tagsElement = element.GetElement("tags"); - - Result result = new() - { - Algorithm = tagsElement.HasValue ? tagsElement.GetString("algorithm") : "other", - Duration = element.GetDouble("duration"), - Language = element.GetString("implementation"), - IsFaithful = tagsElement.HasValue && tagsElement.GetString("faithful")?.ToLower() == "yes", - Label = element.GetString("label"), - Passes = element.GetInt64("passes"), - Solution = element.GetString("solution"), - Threads = element.GetInt32("threads"), - Status = element.GetString("status") - }; - - if (tagsElement.HasValue && int.TryParse(tagsElement.Value.GetString("bits"), out int bits)) - { - result.Bits = bits; - } - - return result; - } - - public async Task GetReport(string id) - { - return await LoadReportJsonFile(id) ?? new Report(); - } - - public async Task<(ReportSummary[] summaries, int total)> GetSummaries(int maxSummaryCount) - { - return await GetSummaries(null, 0, maxSummaryCount); - } - - public async Task<(ReportSummary[] summaries, int total)> GetSummaries(string? runnerId, int skipFirst, int maxSummaryCount) - { - await LoadReportJsonFiles(skipFirst + maxSummaryCount); - - return (this.summaries!.Skip(skipFirst).Take(maxSummaryCount).ToArray(), totalReports); - } - - public void FlushCache() - { - this.totalReports = 0; - this.haveJsonFilesLoaded = false; - this.summaries = null; - this.reportMap = null; - this.reachedMaxFileCount = false; - } - - public Task GetRunners() - { - return Task.FromResult(Array.Empty()); - } - } + public class ReportReader : IReportReader + { + private List? summaries; + private Dictionary? reportMap; + private readonly HttpClient httpClient; + private readonly string? indexFileName; + private readonly bool isS3Bucket; + private bool haveJsonFilesLoaded = false; + private bool reachedMaxFileCount = false; + private int totalReports = 0; + + public ReportReader(string baseAddress, IConfiguration configuration) + { + this.httpClient = new HttpClient { BaseAddress = new Uri(configuration.GetValue(Constants.BaseURI, baseAddress) ?? baseAddress) }; + this.indexFileName = configuration.GetValue(Constants.Index, null); + this.isS3Bucket = configuration.GetValue(Constants.IsS3Bucket, false); + } + + private async Task LoadReportJsonFile(string fileName) + { + if (string.IsNullOrEmpty(fileName)) + return null; + + if (haveJsonFilesLoaded && reportMap!.TryGetValue(fileName, out Report? report)) + return report; + + string reportJson; + + try + { + reportJson = await this.httpClient.GetStringAsync(fileName); + } + catch + { + return null; + } + + var reportElement = JsonDocument.Parse(reportJson).RootElement; + return ParseReportElement(reportJson, reportElement, fileName); + } + + private async Task LoadReportJsonFiles(int maxFileCount) + { + if (this.haveJsonFilesLoaded && (!this.reachedMaxFileCount || maxFileCount <= this.reportMap!.Count)) + return; + + string[]? reportFileNames = null; + + if (this.isS3Bucket) + reportFileNames = await S3BucketIndexReader.GetFileNames(this.httpClient); + + else if (this.indexFileName != null) + { + try + { + reportFileNames = JsonSerializer.Deserialize(await this.httpClient.GetStringAsync(this.indexFileName)); + } + catch { } + } + + this.summaries = []; + this.reportMap = []; + this.reachedMaxFileCount = false; + this.totalReports = reportFileNames?.Length ?? maxFileCount; + + Dictionary> stringReaderMap = []; + + for (int fileIndex = 0; fileIndex != reportFileNames?.Length; fileIndex++) + { + string fileName = reportFileNames != null ? reportFileNames[fileIndex] : $"data/report{fileIndex + 1}.json"; + + stringReaderMap[fileName] = this.httpClient.GetStringAsync(fileName); + + if (--maxFileCount <= 0) + { + this.reachedMaxFileCount = true; + break; + } + } + + foreach (var item in stringReaderMap) + { + string reportJson; + string fileName = item.Key; + + try + { + reportJson = await item.Value; + } + catch (HttpRequestException) + { + // break out of the loop on error if we're not reading a list of files we retrieved from the index file... + if (reportFileNames == null) + break; + + // ...otherwise try reading the next file + else + continue; + } + + try + { + var reportElement = JsonDocument.Parse(reportJson).RootElement; + Report report = ParseReportElement(reportJson, reportElement, fileName); + + this.reportMap[fileName] = report; + this.summaries.Add(ExtractSummary(report)); + } + catch + { + Console.WriteLine($"Report parsing of file {fileName} failed"); + } + } + + this.haveJsonFilesLoaded = true; + } + + private static ReportSummary ExtractSummary(Report report) + { + return new() + { + Id = report.Id, + Architecture = report.OperatingSystem?.Architecture, + CpuBrand = report.CPU?.Brand, + CpuCores = report.CPU?.Cores, + CpuProcessors = report.CPU?.Processors, + CpuVendor = report.CPU?.Vendor, + Date = report.Date, + DockerArchitecture = report.DockerInfo?.Architecture, + IsSystemVirtual = report.System?.IsVirtual, + OsPlatform = report.OperatingSystem?.Platform, + OsDistro = report.OperatingSystem?.Distribution, + OsRelease = report.OperatingSystem?.Release, + ResultCount = report.Results?.Length ?? 0, + User = report.User + }; + } + + private static Report ParseReportElement(string json, JsonElement element, string? id = null) + { + var machineElement = element.GetElement("machine"); + var metadataElement = element.GetElement("metadata"); + + Report report = new() + { + Id = metadataElement.GetString("id") ?? id ?? json.GetStableHashCode().ToString(), + Date = metadataElement.GetDateFromUnixTimeSeconds("date"), + User = metadataElement.GetString("user"), + CPU = machineElement.Get("cpu"), + OperatingSystem = machineElement.Get("os"), + System = machineElement.Get("system"), + DockerInfo = machineElement.Get("docker") + }; + + var resultsElement = element.GetElement("results"); + + List results = []; + + if (resultsElement.HasValue && resultsElement.Value.ValueKind == JsonValueKind.Array) + { + foreach (var resultElement in resultsElement.Value.EnumerateArray()) + { + results.Add(ParseResultElement(resultElement)); + } + } + + report.Results = [.. results]; + + return report; + } + + private static Result ParseResultElement(JsonElement element) + { + var tagsElement = element.GetElement("tags"); + + Result result = new() + { + Algorithm = tagsElement.HasValue ? tagsElement.GetString("algorithm") : "other", + Duration = element.GetDouble("duration"), + Language = element.GetString("implementation"), + IsFaithful = tagsElement.HasValue && tagsElement.GetString("faithful")?.ToLower() == "yes", + Label = element.GetString("label"), + Passes = element.GetInt64("passes"), + Solution = element.GetString("solution"), + Threads = element.GetInt32("threads"), + Status = element.GetString("status") + }; + + if (tagsElement.HasValue && int.TryParse(tagsElement.Value.GetString("bits"), out int bits)) + { + result.Bits = bits; + } + + return result; + } + + public async Task GetReport(string id) + { + return await LoadReportJsonFile(id) ?? new Report(); + } + + public async Task<(ReportSummary[] summaries, int total)> GetSummaries(int maxSummaryCount) + { + return await GetSummaries(null, 0, maxSummaryCount); + } + + public async Task<(ReportSummary[] summaries, int total)> GetSummaries(string? runnerId, int skipFirst, int maxSummaryCount) + { + await LoadReportJsonFiles(skipFirst + maxSummaryCount); + + return (this.summaries!.Skip(skipFirst).Take(maxSummaryCount).ToArray(), totalReports); + } + + public void FlushCache() + { + this.totalReports = 0; + this.haveJsonFilesLoaded = false; + this.summaries = null; + this.reportMap = null; + this.reachedMaxFileCount = false; + } + + public Task GetRunners() + { + return Task.FromResult(Array.Empty()); + } + } } diff --git a/src/JsonFileReader/S3BucketIndexReader.cs b/src/JsonFileReader/S3BucketIndexReader.cs index 45b7cce..e25a481 100644 --- a/src/JsonFileReader/S3BucketIndexReader.cs +++ b/src/JsonFileReader/S3BucketIndexReader.cs @@ -8,28 +8,28 @@ namespace PrimeView.JsonFileReader { - static class S3BucketIndexReader - { - public static async Task GetFileNames(HttpClient httpClient) - { - XElement indexElement; + static class S3BucketIndexReader + { + public static async Task GetFileNames(HttpClient httpClient) + { + XElement indexElement; - try - { - indexElement = XElement.Parse(await httpClient.GetStringAsync("")); - } - catch - { - return null; - } + try + { + indexElement = XElement.Parse(await httpClient.GetStringAsync("")); + } + catch + { + return null; + } - XNamespace ns = indexElement.GetDefaultNamespace(); + XNamespace ns = indexElement.GetDefaultNamespace(); - return indexElement.Descendants(ns + "Contents")? - .Select(element => new KeyValuePair(element.Element(ns + "Key")!.Value, XmlConvert.ToDateTime(element.Element(ns + "LastModified")!.Value, XmlDateTimeSerializationMode.Utc))) - .OrderByDescending(pair => pair.Value) - .Select(pair => pair.Key) - .ToArray(); - } - } + return indexElement.Descendants(ns + "Contents")? + .Select(element => new KeyValuePair(element.Element(ns + "Key")!.Value, XmlConvert.ToDateTime(element.Element(ns + "LastModified")!.Value, XmlDateTimeSerializationMode.Utc))) + .OrderByDescending(pair => pair.Value) + .Select(pair => pair.Key) + .ToArray(); + } + } } diff --git a/src/RestAPIReader/Constants.cs b/src/RestAPIReader/Constants.cs index 5c0100f..8f129c4 100644 --- a/src/RestAPIReader/Constants.cs +++ b/src/RestAPIReader/Constants.cs @@ -1,7 +1,7 @@ namespace PrimeView.RestAPIReader { - static class Constants - { - public const string APIBaseURI = nameof(APIBaseURI); - } + static class Constants + { + public const string APIBaseURI = nameof(APIBaseURI); + } } diff --git a/src/RestAPIReader/ExtensionMethods.cs b/src/RestAPIReader/ExtensionMethods.cs index 9337115..397a2a1 100644 --- a/src/RestAPIReader/ExtensionMethods.cs +++ b/src/RestAPIReader/ExtensionMethods.cs @@ -4,12 +4,12 @@ namespace PrimeView.RestAPIReader { - public static class ExtensionMethods - { - public static IServiceCollection AddRestAPIReportReader(this IServiceCollection serviceCollection, IConfiguration configuration) - { - return serviceCollection.AddScoped(sp => new ReportReader(configuration)); - } + public static class ExtensionMethods + { + public static IServiceCollection AddRestAPIReportReader(this IServiceCollection serviceCollection, IConfiguration configuration) + { + return serviceCollection.AddScoped(sp => new ReportReader(configuration)); + } - } + } } diff --git a/src/RestAPIReader/ReportReader.cs b/src/RestAPIReader/ReportReader.cs index 0ee8861..65e31e2 100644 --- a/src/RestAPIReader/ReportReader.cs +++ b/src/RestAPIReader/ReportReader.cs @@ -1,6 +1,5 @@ using Microsoft.Extensions.Configuration; using PrimeView.Entities; -using PrimeView.RestAPIReader; using System; using System.Collections.Generic; using System.Linq; @@ -9,188 +8,187 @@ namespace PrimeView.RestAPIReader { - public class ReportReader : IReportReader - { - private readonly Dictionary> summaryMap = new(); - private readonly Dictionary reportMap = new(); - private readonly Service.PrimesAPI primesAPI; - private readonly Dictionary totalReportsMap = new(); - - public ReportReader(IConfiguration configuration) - { - this.primesAPI = new(new HttpClient()); - if (!string.IsNullOrEmpty(configuration[Constants.APIBaseURI])) - this.primesAPI.BaseUrl = configuration.GetValue(Constants.APIBaseURI); - } - - private async Task<(SortedList summaries, int totalReports)> LoadMissingSummaries(string? runnerId, int skipFirst, int maxSummaryCount) - { - if (runnerId == null) - runnerId = string.Empty; - - if (!this.summaryMap.ContainsKey(runnerId)) - this.summaryMap.Add(runnerId, new SortedList()); - - var summaries = summaryMap[runnerId]; + public class ReportReader : IReportReader + { + private readonly Dictionary> summaryMap = []; + private readonly Dictionary reportMap = []; + private readonly Service.PrimesAPI primesAPI; + private readonly Dictionary totalReportsMap = []; + + public ReportReader(IConfiguration configuration) + { + this.primesAPI = new(new HttpClient()); + if (!string.IsNullOrEmpty(configuration[Constants.APIBaseURI])) + this.primesAPI.BaseUrl = configuration.GetValue(Constants.APIBaseURI); + } + + private async Task<(SortedList summaries, int totalReports)> LoadMissingSummaries(string? runnerId, int skipFirst, int maxSummaryCount) + { + runnerId ??= string.Empty; + + if (!this.summaryMap.ContainsKey(runnerId)) + this.summaryMap.Add(runnerId, []); + + var summaries = summaryMap[runnerId]; for (int missingIndex = skipFirst; missingIndex < skipFirst + maxSummaryCount; missingIndex++) - { - // find gaps in the requested key space, and fill them - if (!summaries.ContainsKey(missingIndex)) - { - int missingCount = 0; - - // count number of missing keys, but stop when we've reached the end of the key space we were asked to load - while (!summaries.ContainsKey(missingIndex + ++missingCount) && (missingIndex + missingCount) < (skipFirst + maxSummaryCount)); - - await LoadSummaries(summaries, runnerId, missingIndex, missingCount); - - // we may not actually have been able to load all requested missing summaries, but for the sake of filling gaps - // for which data is available in an efficient manner, we'll act like we did; it just means some gaps may remain - missingIndex += missingCount; - } - } - - return (summaries, this.totalReportsMap[runnerId]); - } - - private async Task LoadSummaries(SortedList summaries, string runnerId, int skipFirst, int maxSummaryCount) - { - Service.Sessions sessionsResult; - try - { - if (string.IsNullOrWhiteSpace(runnerId) || !int.TryParse(runnerId, out int parsedRunnerId)) - sessionsResult = await this.primesAPI.GetSessionsAsync(skipFirst, maxSummaryCount, null); - else - sessionsResult = await this.primesAPI.GetRunnerSessionsAsync(parsedRunnerId, skipFirst, maxSummaryCount); - } - catch (Service.ApiException e) - { - Console.Error.WriteLine(e); - return; - } - - int i = 0; - foreach (var session in sessionsResult.Data) - { - var runner = session.Runner; - - ReportSummary summary = new() - { - Id = session.Id, - Date = session.Created_at.DateTime, - User = runner.Name, - Architecture = runner.Os_arch, - CpuBrand = runner.Cpu_brand, - CpuCores = (int)runner.Cpu_cores, - CpuProcessors = (int)runner.Cpu_processors, - CpuVendor = runner.Cpu_vendor, - DockerArchitecture = runner.Docker_architecture, - IsSystemVirtual = runner.System_virtual, - OsPlatform = runner.Os_platform, - OsDistro = runner.Os_distro, - OsRelease = runner.Os_release, - ResultCount = (int)session.Results_count - }; - - summaries.Add(skipFirst + i++, summary); - } - - this.totalReportsMap[runnerId] = sessionsResult.Total; - } - - public async Task GetReport(string id) - { - if (this.reportMap.ContainsKey(id)) - return this.reportMap[id]; - - Service.Session? sessionResponse; - try - { - sessionResponse = await this.primesAPI.GetSessionResultsAsync(int.Parse(id)); - } - catch (Service.ApiException e) - { - Console.Error.WriteLine(e); - return new Report(); - } - - (CPUInfo cpu, SystemInfo system, OperatingSystemInfo operatingSystem, DockerInfo dockerInfo) = ParseRunner(sessionResponse.Runner); - - List results = new(); - foreach(var apiResult in sessionResponse.Results) - { - Result result = new() - { - Algorithm = apiResult.Algorithm, - Duration = apiResult.Duration, - IsFaithful = apiResult.Faithful, - Label = apiResult.Label, - Language = apiResult.Implementation, - Passes = (long)apiResult.Passes, - Solution = apiResult.Solution, - Threads = apiResult.Threads - }; - - if (int.TryParse(apiResult.Bits, out int bits)) - result.Bits = bits; - - results.Add(result); - } - - Report report = new() - { - Id = sessionResponse.Id, - Date = sessionResponse.Created_at.DateTime, - User = sessionResponse.Runner.Name, - CPU = cpu, - System = system, - OperatingSystem = operatingSystem, - DockerInfo = dockerInfo, - Results = results.ToArray() - }; - - this.reportMap[id] = report; - - return report; - } - - public async Task GetRunners() - { - Service.Runners runnersResponse; - - try - { - runnersResponse = await this.primesAPI.GetRunnersAsync(0, 100); - } - catch (Service.ApiException e) - { - Console.Error.WriteLine(e); - return Array.Empty(); + { + // find gaps in the requested key space, and fill them + if (!summaries.ContainsKey(missingIndex)) + { + int missingCount = 0; + + // count number of missing keys, but stop when we've reached the end of the key space we were asked to load + while (!summaries.ContainsKey(missingIndex + ++missingCount) && (missingIndex + missingCount) < (skipFirst + maxSummaryCount)) ; + + await LoadSummaries(summaries, runnerId, missingIndex, missingCount); + + // we may not actually have been able to load all requested missing summaries, but for the sake of filling gaps + // for which data is available in an efficient manner, we'll act like we did; it just means some gaps may remain + missingIndex += missingCount; + } + } + + return (summaries, this.totalReportsMap[runnerId]); + } + + private async Task LoadSummaries(SortedList summaries, string runnerId, int skipFirst, int maxSummaryCount) + { + Service.Sessions sessionsResult; + try + { + if (string.IsNullOrWhiteSpace(runnerId) || !int.TryParse(runnerId, out int parsedRunnerId)) + sessionsResult = await this.primesAPI.GetSessionsAsync(skipFirst, maxSummaryCount, null); + else + sessionsResult = await this.primesAPI.GetRunnerSessionsAsync(parsedRunnerId, skipFirst, maxSummaryCount); + } + catch (Service.ApiException e) + { + Console.Error.WriteLine(e); + return; + } + + int i = 0; + foreach (var session in sessionsResult.Data) + { + var runner = session.Runner; + + ReportSummary summary = new() + { + Id = session.Id, + Date = session.Created_at.DateTime, + User = runner.Name, + Architecture = runner.Os_arch, + CpuBrand = runner.Cpu_brand, + CpuCores = (int)runner.Cpu_cores, + CpuProcessors = (int)runner.Cpu_processors, + CpuVendor = runner.Cpu_vendor, + DockerArchitecture = runner.Docker_architecture, + IsSystemVirtual = runner.System_virtual, + OsPlatform = runner.Os_platform, + OsDistro = runner.Os_distro, + OsRelease = runner.Os_release, + ResultCount = (int)session.Results_count + }; + + summaries.Add(skipFirst + i++, summary); + } + + this.totalReportsMap[runnerId] = sessionsResult.Total; + } + + public async Task GetReport(string id) + { + if (reportMap.TryGetValue(id, out Report? value)) + return value; + + Service.Session? sessionResponse; + try + { + sessionResponse = await this.primesAPI.GetSessionResultsAsync(int.Parse(id)); + } + catch (Service.ApiException e) + { + Console.Error.WriteLine(e); + return new Report(); } - List runners = new(); + (CPUInfo cpu, SystemInfo system, OperatingSystemInfo operatingSystem, DockerInfo dockerInfo) = ParseRunner(sessionResponse.Runner); - foreach (var runner in runnersResponse.Data) - { + List results = []; + foreach (var apiResult in sessionResponse.Results) + { + Result result = new() + { + Algorithm = apiResult.Algorithm, + Duration = apiResult.Duration, + IsFaithful = apiResult.Faithful, + Label = apiResult.Label, + Language = apiResult.Implementation, + Passes = (long)apiResult.Passes, + Solution = apiResult.Solution, + Threads = apiResult.Threads + }; + + if (int.TryParse(apiResult.Bits, out int bits)) + result.Bits = bits; + + results.Add(result); + } + + Report report = new() + { + Id = sessionResponse.Id, + Date = sessionResponse.Created_at.DateTime, + User = sessionResponse.Runner.Name, + CPU = cpu, + System = system, + OperatingSystem = operatingSystem, + DockerInfo = dockerInfo, + Results = [.. results] + }; + + this.reportMap[id] = report; + + return report; + } + + public async Task GetRunners() + { + Service.Runners runnersResponse; + + try + { + runnersResponse = await this.primesAPI.GetRunnersAsync(0, 100); + } + catch (Service.ApiException e) + { + Console.Error.WriteLine(e); + return []; + } + + List runners = []; + + foreach (var runner in runnersResponse.Data) + { (CPUInfo cpu, SystemInfo system, OperatingSystemInfo operatingSystem, DockerInfo dockerInfo) = ParseRunner(runner); - runners.Add(new() - { - Id = runner.Id, - User = runner.Name, - CPU = cpu, - System = system, - OperatingSystem = operatingSystem, - DockerInfo = dockerInfo - }); - } - - return runners.ToArray(); + runners.Add(new() + { + Id = runner.Id, + User = runner.Name, + CPU = cpu, + System = system, + OperatingSystem = operatingSystem, + DockerInfo = dockerInfo + }); + } + + return [.. runners]; } private (CPUInfo cpu, SystemInfo system, OperatingSystemInfo operatingSystem, DockerInfo dockerInfo) ParseRunner(Service.Runner runner) - { + { CPUInfo cpu = new() { Brand = runner.Cpu_brand, @@ -216,7 +214,7 @@ public async Task GetRunners() Voltage = runner.Cpu_voltage }; - Dictionary cache = new(); + Dictionary cache = []; if (runner.Cpu_cache_l1d != null) cache["l1d"] = (long)runner.Cpu_cache_l1d; @@ -269,25 +267,25 @@ public async Task GetRunners() TotalMemory = (long)runner.Docker_mem_total }; - return (cpu, system, operatingSystem, dockerInfo); + return (cpu, system, operatingSystem, dockerInfo); } public async Task<(ReportSummary[] summaries, int total)> GetSummaries(int maxSummaryCount) - { - return await GetSummaries(null, 0, maxSummaryCount); - } - - public async Task<(ReportSummary[] summaries, int total)> GetSummaries(string? runnerId, int skipFirst, int maxSummaryCount) - { - var result = await LoadMissingSummaries(string.IsNullOrWhiteSpace(runnerId) ? null : runnerId, skipFirst, maxSummaryCount); - - return (result.summaries.SkipWhile(pair => pair.Key < skipFirst).TakeWhile(pair => pair.Key < skipFirst + maxSummaryCount).Select(pair => pair.Value).ToArray(), result.totalReports); - } - - public void FlushCache() - { - this.summaryMap.Clear(); - this.reportMap.Clear(); - } - } + { + return await GetSummaries(null, 0, maxSummaryCount); + } + + public async Task<(ReportSummary[] summaries, int total)> GetSummaries(string? runnerId, int skipFirst, int maxSummaryCount) + { + var (summaries, totalReports) = await LoadMissingSummaries(string.IsNullOrWhiteSpace(runnerId) ? null : runnerId, skipFirst, maxSummaryCount); + + return (summaries.SkipWhile(pair => pair.Key < skipFirst).TakeWhile(pair => pair.Key < skipFirst + maxSummaryCount).Select(pair => pair.Value).ToArray(), totalReports); + } + + public void FlushCache() + { + this.summaryMap.Clear(); + this.reportMap.Clear(); + } + } }