From bd811c861c2883f37912ea87c034fdca392abfc9 Mon Sep 17 00:00:00 2001 From: Ben Parsons <9parsonsb@gmail.com> Date: Sun, 12 May 2024 12:55:28 +1000 Subject: [PATCH] Update Journal File Handling Now Correctly deserializes Journal events --- .../Converters/FleetCarrierTravelConverter.cs | 12 +- .../Files/Converters/IntBoolFlexConverter.cs | 12 +- .../Converters/LegacyFactionConverter.cs | 4 +- .../MaterialCompositionConverter.cs | 7 +- .../Files/Converters/MaterialConverter.cs | 52 +-- .../Converters/MissionEffectConverter.cs | 2 +- .../MutableStringDoubleConverter.cs | 5 +- .../Files/Converters/PipConverter.cs | 2 +- .../Files/Converters/RepInfConverter.cs | 2 +- .../Files/Converters/StarPosConverter.cs | 2 +- .../Converters/StationServiceConverter.cs | 2 +- .../Files/Converters/StringIntConverter.cs | 2 +- .../ThargoidWarRemainingTimeConverter.cs | 24 +- .../Files/Converters/VoucherTypeConverter.cs | 16 +- .../Files/Journal/Travel/FSDJump.cs | 10 +- .../Files/Journal/Travel/Location.cs | 11 +- Pulsar/Features/FileHandlerService.cs | 2 +- Pulsar/Features/FileWatcherService.cs | 49 ++- Pulsar/Features/Journal/JournalService.cs | 84 ++--- Pulsar/Features/Status/StatusService.cs | 32 +- Pulsar/Utils/JournalJsonConverter.cs | 301 ++++++++++++++++++ Pulsar/Utils/JournalReader.cs | 70 ---- Pulsar/WebApp/.vscode/settings.json | 10 + Pulsar/WebApp/src/lib/JournalLog.svelte | 186 +++-------- Pulsar/WebApp/src/lib/JournalService.ts | 1 + Pulsar/WebApp/src/lib/Status.svelte | 65 ++-- Pulsar/WebApp/src/routes/+page.svelte | 50 +-- .../src/routes/explorer/Explorer.svelte | 133 ++++++++ Pulsar/WebApp/src/types/api/Status.ts | 8 +- 29 files changed, 714 insertions(+), 442 deletions(-) create mode 100644 Pulsar/Utils/JournalJsonConverter.cs delete mode 100644 Pulsar/Utils/JournalReader.cs create mode 100644 Pulsar/WebApp/.vscode/settings.json create mode 100644 Pulsar/WebApp/src/lib/JournalService.ts create mode 100644 Pulsar/WebApp/src/routes/explorer/Explorer.svelte diff --git a/ObservatoryFramework/Files/Converters/FleetCarrierTravelConverter.cs b/ObservatoryFramework/Files/Converters/FleetCarrierTravelConverter.cs index 3b3a412..0201df0 100644 --- a/ObservatoryFramework/Files/Converters/FleetCarrierTravelConverter.cs +++ b/ObservatoryFramework/Files/Converters/FleetCarrierTravelConverter.cs @@ -7,13 +7,13 @@ class FleetCarrierTravelConverter : JsonConverter { public override float Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.String) - return float.Parse(reader.GetString().Split(' ')[0]); - return reader.GetSingle(); - } + if (reader.TokenType == JsonTokenType.String) + return float.Parse(reader.GetString().Split(' ')[0]); + return reader.GetSingle(); + } public override void Write(Utf8JsonWriter writer, float value, JsonSerializerOptions options) { - writer.WriteStringValue(value.ToString()); - } + writer.WriteStringValue(value.ToString()); + } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/IntBoolFlexConverter.cs b/ObservatoryFramework/Files/Converters/IntBoolFlexConverter.cs index 58b84bb..b497d22 100644 --- a/ObservatoryFramework/Files/Converters/IntBoolFlexConverter.cs +++ b/ObservatoryFramework/Files/Converters/IntBoolFlexConverter.cs @@ -7,13 +7,13 @@ public class IntBoolFlexConverter : JsonConverter { public override bool Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.Number) - return reader.GetInt16() == 1; - return reader.GetBoolean(); - } + if (reader.TokenType == JsonTokenType.Number) + return reader.GetInt16() == 1; + return reader.GetBoolean(); + } public override void Write(Utf8JsonWriter writer, bool value, JsonSerializerOptions options) { - writer.WriteBooleanValue(value); - } + writer.WriteBooleanValue(value); + } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/LegacyFactionConverter.cs b/ObservatoryFramework/Files/Converters/LegacyFactionConverter.cs index 70c8e81..7f0b24b 100644 --- a/ObservatoryFramework/Files/Converters/LegacyFactionConverter.cs +++ b/ObservatoryFramework/Files/Converters/LegacyFactionConverter.cs @@ -21,6 +21,6 @@ public class LegacyFactionConverter : JsonConverter where TF public override void Write(Utf8JsonWriter writer, TFaction value, JsonSerializerOptions options) { - throw new NotImplementedException(); - } + JsonSerializer.Serialize(writer, value, options); + } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/MaterialCompositionConverter.cs b/ObservatoryFramework/Files/Converters/MaterialCompositionConverter.cs index 565f56f..440c923 100644 --- a/ObservatoryFramework/Files/Converters/MaterialCompositionConverter.cs +++ b/ObservatoryFramework/Files/Converters/MaterialCompositionConverter.cs @@ -41,11 +41,12 @@ public class MaterialCompositionConverter : JsonConverter)JsonSerializer.Deserialize(ref reader, typeof(ImmutableList)); + return JsonSerializer.Deserialize>(ref reader, options)!; } - public override void Write(Utf8JsonWriter writer, ImmutableList value, JsonSerializerOptions options) + public override void Write(Utf8JsonWriter writer, ImmutableList value, + JsonSerializerOptions options) { - throw new NotImplementedException(); + JsonSerializer.Serialize(writer, value, options); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/MaterialConverter.cs b/ObservatoryFramework/Files/Converters/MaterialConverter.cs index 7bee43b..f005bdb 100644 --- a/ObservatoryFramework/Files/Converters/MaterialConverter.cs +++ b/ObservatoryFramework/Files/Converters/MaterialConverter.cs @@ -11,42 +11,44 @@ namespace Observatory.Framework.Files.Converters; /// public class MaterialConverter : JsonConverter> { - public override ImmutableList Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + public override ImmutableList Read(ref Utf8JsonReader reader, Type typeToConvert, + JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.StartObject) + if (reader.TokenType == JsonTokenType.StartObject) + { + var materialComposition = new List(); + while (reader.Read()) { - var materialComposition = new List(); - while (reader.Read()) + if (reader.TokenType != JsonTokenType.EndObject) { - if (reader.TokenType != JsonTokenType.EndObject) + if (reader.TokenType == JsonTokenType.PropertyName) { - if (reader.TokenType == JsonTokenType.PropertyName) + var name = reader.GetString(); + reader.Read(); + var count = reader.GetInt32(); + var material = new Material { - var name = reader.GetString(); - reader.Read(); - var count = reader.GetInt32(); - var material = new Material - { - Name = name, - Name_Localised = name, - Count = count - }; - materialComposition.Add(material); - } - } - else - { - break; + Name = name, + Name_Localised = name, + Count = count + }; + materialComposition.Add(material); } } - return materialComposition.ToImmutableList(); + else + { + break; + } } - return (ImmutableList)JsonSerializer.Deserialize(ref reader, typeof(ImmutableList)); + return materialComposition.ToImmutableList(); } + return JsonSerializer.Deserialize>(ref reader, options)!; + } + public override void Write(Utf8JsonWriter writer, ImmutableList value, JsonSerializerOptions options) { - throw new NotImplementedException(); - } + JsonSerializer.Serialize(writer, value, options); + } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/MissionEffectConverter.cs b/ObservatoryFramework/Files/Converters/MissionEffectConverter.cs index b54be24..d5bc871 100644 --- a/ObservatoryFramework/Files/Converters/MissionEffectConverter.cs +++ b/ObservatoryFramework/Files/Converters/MissionEffectConverter.cs @@ -32,6 +32,6 @@ public class MissionEffectConverter : JsonConverter public override void Write(Utf8JsonWriter writer, MissionEffect value, JsonSerializerOptions options) { - throw new NotImplementedException(); + JsonSerializer.Serialize(writer, value, options); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/MutableStringDoubleConverter.cs b/ObservatoryFramework/Files/Converters/MutableStringDoubleConverter.cs index 7ea1a96..7bbfe6f 100644 --- a/ObservatoryFramework/Files/Converters/MutableStringDoubleConverter.cs +++ b/ObservatoryFramework/Files/Converters/MutableStringDoubleConverter.cs @@ -14,6 +14,9 @@ class MutableStringDoubleConverter : JsonConverter public override void Write(Utf8JsonWriter writer, object value, JsonSerializerOptions options) { - throw new NotImplementedException(); + if (value.GetType() == typeof(string)) + writer.WriteStringValue((string)value); + else + writer.WriteNumberValue((double)value); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/PipConverter.cs b/ObservatoryFramework/Files/Converters/PipConverter.cs index 27020d2..07c572a 100644 --- a/ObservatoryFramework/Files/Converters/PipConverter.cs +++ b/ObservatoryFramework/Files/Converters/PipConverter.cs @@ -14,6 +14,6 @@ class PipConverter : JsonConverter<(int Sys, int Eng, int Wep)> public override void Write(Utf8JsonWriter writer, (int Sys, int Eng, int Wep) value, JsonSerializerOptions options) { - JsonSerializer.Serialize(writer, new[] { value.Sys, value.Eng, value.Wep }); + JsonSerializer.Serialize(writer, new {value.Sys, value.Eng, value.Wep}, options); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/RepInfConverter.cs b/ObservatoryFramework/Files/Converters/RepInfConverter.cs index 52eba38..7e8d341 100644 --- a/ObservatoryFramework/Files/Converters/RepInfConverter.cs +++ b/ObservatoryFramework/Files/Converters/RepInfConverter.cs @@ -12,6 +12,6 @@ public class RepInfConverter : JsonConverter public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) { - throw new NotImplementedException(); + JsonSerializer.Serialize(writer, value, options); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/StarPosConverter.cs b/ObservatoryFramework/Files/Converters/StarPosConverter.cs index ddbf60b..5dd1c37 100644 --- a/ObservatoryFramework/Files/Converters/StarPosConverter.cs +++ b/ObservatoryFramework/Files/Converters/StarPosConverter.cs @@ -17,6 +17,6 @@ public class StarPosConverter : JsonConverter<(double x, double y, double z)> public override void Write(Utf8JsonWriter writer, (double x, double y, double z) value, JsonSerializerOptions options) { - throw new NotImplementedException(); + JsonSerializer.Serialize(writer, value, options); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/StationServiceConverter.cs b/ObservatoryFramework/Files/Converters/StationServiceConverter.cs index 3c2e543..5a524b1 100644 --- a/ObservatoryFramework/Files/Converters/StationServiceConverter.cs +++ b/ObservatoryFramework/Files/Converters/StationServiceConverter.cs @@ -21,6 +21,6 @@ public class StationServiceConverter : JsonConverter public override void Write(Utf8JsonWriter writer, StationService value, JsonSerializerOptions options) { - throw new NotImplementedException(); + JsonSerializer.Serialize(writer, value, options); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/StringIntConverter.cs b/ObservatoryFramework/Files/Converters/StringIntConverter.cs index c29dbe7..3bc28a6 100644 --- a/ObservatoryFramework/Files/Converters/StringIntConverter.cs +++ b/ObservatoryFramework/Files/Converters/StringIntConverter.cs @@ -14,6 +14,6 @@ class StringIntConverter : JsonConverter public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) { - writer.WriteStringValue(value.ToString()); + JsonSerializer.Serialize(writer, value, options); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/ThargoidWarRemainingTimeConverter.cs b/ObservatoryFramework/Files/Converters/ThargoidWarRemainingTimeConverter.cs index bec4040..0c81d7f 100644 --- a/ObservatoryFramework/Files/Converters/ThargoidWarRemainingTimeConverter.cs +++ b/ObservatoryFramework/Files/Converters/ThargoidWarRemainingTimeConverter.cs @@ -7,22 +7,22 @@ class ThargoidWarRemainingTimeConverter : JsonConverter { public override int Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - if (reader.TokenType == JsonTokenType.String) - { - var value = reader.GetString(); - - var dayCount = int.TryParse(value.Split(' ')[0], out var days) - ? days - : 0; + if (reader.TokenType == JsonTokenType.String) + { + var value = reader.GetString(); - return dayCount; - } + var dayCount = int.TryParse(value.Split(' ')[0], out var days) + ? days + : 0; - return reader.GetInt32(); + return dayCount; } + return reader.GetInt32(); + } + public override void Write(Utf8JsonWriter writer, int value, JsonSerializerOptions options) { - writer.WriteStringValue(value + " Days"); - } + writer.WriteStringValue(value + " Days"); + } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Converters/VoucherTypeConverter.cs b/ObservatoryFramework/Files/Converters/VoucherTypeConverter.cs index bb33806..0255c46 100644 --- a/ObservatoryFramework/Files/Converters/VoucherTypeConverter.cs +++ b/ObservatoryFramework/Files/Converters/VoucherTypeConverter.cs @@ -8,18 +8,18 @@ class VoucherTypeConverter : JsonConverter { public override VoucherType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) { - var voucher = reader.GetString(); + var voucher = reader.GetString(); - if (voucher.Length == 0) - voucher = "None"; + if (voucher.Length == 0) + voucher = "None"; - var missionEffect = (VoucherType)Enum.Parse(typeof(VoucherType), voucher, true); + var missionEffect = (VoucherType)Enum.Parse(typeof(VoucherType), voucher, true); - return missionEffect; - } + return missionEffect; + } public override void Write(Utf8JsonWriter writer, VoucherType value, JsonSerializerOptions options) { - throw new NotImplementedException(); - } + JsonSerializer.Serialize(writer, value, options); + } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Journal/Travel/FSDJump.cs b/ObservatoryFramework/Files/Journal/Travel/FSDJump.cs index a67bda2..01f4ed9 100644 --- a/ObservatoryFramework/Files/Journal/Travel/FSDJump.cs +++ b/ObservatoryFramework/Files/Journal/Travel/FSDJump.cs @@ -20,15 +20,7 @@ public class FSDJump : JournalBase public int BoostUsed { get; init; } [JsonConverter(typeof(LegacyFactionConverter))] public SystemFaction SystemFaction { get; init; } - [Obsolete(JournalUtilities.ObsoleteMessage)] - public string FactionState - { - get => SystemFaction.FactionState; - init - { - //Stale Data, discard - } - } + public string SystemAllegiance { get; init; } public string SystemEconomy { get; init; } public string SystemEconomy_Localised { get; init; } diff --git a/ObservatoryFramework/Files/Journal/Travel/Location.cs b/ObservatoryFramework/Files/Journal/Travel/Location.cs index b63a20b..6a7fa37 100644 --- a/ObservatoryFramework/Files/Journal/Travel/Location.cs +++ b/ObservatoryFramework/Files/Journal/Travel/Location.cs @@ -10,16 +10,7 @@ public class Location : JournalBase [JsonConverter(typeof(IntBoolFlexConverter))] public bool Docked { get; init; } public double DistFromStarLS { get; init; } - - [Obsolete(JournalUtilities.ObsoleteMessage)] - public string FactionState - { - get => SystemFaction.FactionState; - init - { - //Stale Data, discard - } - } + /// /// Name of the station at which this event occurred. /// diff --git a/Pulsar/Features/FileHandlerService.cs b/Pulsar/Features/FileHandlerService.cs index 436d92d..2d70e84 100644 --- a/Pulsar/Features/FileHandlerService.cs +++ b/Pulsar/Features/FileHandlerService.cs @@ -89,6 +89,6 @@ public class FileHandlerService( } logger.LogInformation("Handling file {FileName} with Type {Type}", fileName, handler.GetType().ToString()); - Task.Run(() => handler.HandleFile(path)); + await handler.HandleFile(path); } } \ No newline at end of file diff --git a/Pulsar/Features/FileWatcherService.cs b/Pulsar/Features/FileWatcherService.cs index 401d2e5..9e9a69a 100644 --- a/Pulsar/Features/FileWatcherService.cs +++ b/Pulsar/Features/FileWatcherService.cs @@ -3,44 +3,67 @@ namespace Pulsar.Features; using System.Collections.Concurrent; using Microsoft.Extensions.FileProviders; -public class FileWatcherService(IOptions options, IFileHandlerService fileHandlerService) : IHostedService +public class FileWatcherService(IOptions options, IFileHandlerService fileHandlerService) + : IHostedService { private PhysicalFileProvider watcher = null!; - + public Task StartAsync(CancellationToken cancellationToken) { if (!Directory.Exists(options.Value.JournalDirectory)) { throw new Exception($"Directory {options.Value.JournalDirectory} does not exist."); } - + watcher = new PhysicalFileProvider(options.Value.JournalDirectory); Watch(); + // read the journal directory to get the initial files +#if DEBUG + Task.Run(() => + { + Thread.Sleep(TimeSpan.FromSeconds(10)); + HandleFileChanged(); + }, cancellationToken); +#else + HandleFileChanged(); +#endif + + return Task.CompletedTask; } - + ConcurrentDictionary FileDates = new(); - private void HandleFileChanged(object? sender) + private void HandleFileChanged(object? sender = null) { foreach (var file in watcher.GetDirectoryContents("")) { - if (file.IsDirectory || !file.Name.EndsWith(".json") && !(file.Name.StartsWith(FileHandlerService.JournalLogFileNameStart) && file.Name.EndsWith(FileHandlerService.JournalLogFileNameEnd))) + if (file.IsDirectory || !file.Name.EndsWith(".json") && + !(file.Name.StartsWith(FileHandlerService.JournalLogFileNameStart) && + file.Name.EndsWith(FileHandlerService.JournalLogFileNameEnd))) { continue; } - - var existing = FileDates.GetOrAdd(file.PhysicalPath, file.LastModified); - - if (existing != file.LastModified) + + FileDates.AddOrUpdate(file.PhysicalPath, _ => { - fileHandlerService.HandleFile(file.PhysicalPath); - } + Task.Run(() => fileHandlerService.HandleFile(file.PhysicalPath)); + return file.LastModified; + }, (_, existing) => + { + if (existing != file.LastModified) + { + Task.Run(() => fileHandlerService.HandleFile(file.PhysicalPath)); + } + + return file.LastModified; + }); } + Watch(); } - + private void Watch() { watcher.Watch("*.*").RegisterChangeCallback(HandleFileChanged, null); diff --git a/Pulsar/Features/Journal/JournalService.cs b/Pulsar/Features/Journal/JournalService.cs index bb9d22c..b959445 100644 --- a/Pulsar/Features/Journal/JournalService.cs +++ b/Pulsar/Features/Journal/JournalService.cs @@ -1,12 +1,12 @@ namespace Pulsar.Features.Journal; +using System.Collections.Concurrent; using System.Text.RegularExpressions; using Observatory.Framework.Files.Journal; public interface IJournalService : IJournalHandler>; -public class JournalService -( +public class JournalService( ILogger logger, IOptions options, IEventHubContext hub, @@ -14,74 +14,52 @@ public class JournalService ) : IJournalService { public string FileName => FileHandlerService.JournalLogFileName; + + static ConcurrentBag _journals = new(); - public Task HandleFile(string filePath) => HandleFile(filePath, CancellationToken.None); - public async Task HandleFile(string filePath, CancellationToken token) + static DateTimeOffset notBefore = DateTimeOffset.UtcNow.AddHours(-1); + + public async Task HandleFile(string filePath) { if (!FileHelper.ValidateFile(filePath)) { return; } - var file = await File.ReadAllLinesAsync(filePath, Encoding.UTF8, token); - var journals = file.Select(line => JsonSerializer.Deserialize(line)).ToList(); - - + var file = await File.ReadAllLinesAsync(filePath, Encoding.UTF8); var newJournals = new List(); - var notBefore = DateTimeOffset.UtcNow.Subtract(TimeSpan.FromHours(6)); - foreach (var journal in journals) + var select = file.AsParallel().Select(line => JsonSerializer.Deserialize(line, + new JsonSerializerOptions + { + PropertyNameCaseInsensitive = true, + Converters = { new JournalConverter(logger) } + })); + + foreach (var journal in select) { - if (context.Journals.Any(j => j.Timestamp == journal.Timestamp && j.Event == journal.Event)) + if (_journals.Any(j => j.Timestamp == journal.Timestamp && j.Event == journal.Event)) + { + continue; + } + + if (journal.Timestamp < notBefore) { continue; } - context.Journals.Add(journal); - - if (journal.Timestamp > notBefore) - { - newJournals.Add(journal); - } + _journals.Add(journal); + newJournals.Add(journal); } - await hub.Clients.All.JournalUpdated(newJournals); + if (newJournals.Any()) + { + await hub.Clients.All.JournalUpdated(newJournals); + } } public async Task> Get() { - var folder = new DirectoryInfo(options.Value.JournalDirectory); - var regex = new Regex(FileHandlerService.JournalLogFileNameRegEx); - - if (!folder.Exists) - { - logger.LogWarning("Journal directory {JournalDirectory} does not exist", folder.FullName); - return []; - } - - var dataFileName = folder.GetFiles().FirstOrDefault(f => regex.IsMatch(f.Name))?.FullName; - - if (!FileHelper.ValidateFile(dataFileName)) - { - return []; - } - - // Seems each entry is a new line. Not sure if this can be relied on? - var logs = File.ReadAllLines(dataFileName); - - var journals = new List(); - foreach (var log in logs) - { - // var info = JournalReader.ObservatoryDeserializer(log); - var info = JsonSerializer.Deserialize(log); - if (info != null) - { - journals.Add(info); - } - } - - if (journals.Count > 0) return journals; - - logger.LogWarning("Failed to deserialize module info file {file}", dataFileName); - return []; + await hub.Clients.All.JournalUpdated(_journals.ToList()); + return _journals.ToList(); } -} +} \ No newline at end of file diff --git a/Pulsar/Features/Status/StatusService.cs b/Pulsar/Features/Status/StatusService.cs index b2198a7..b9b7ec0 100644 --- a/Pulsar/Features/Status/StatusService.cs +++ b/Pulsar/Features/Status/StatusService.cs @@ -13,31 +13,6 @@ public class StatusService { public string FileName => FileHandlerService.StatusFileName; - public bool ValidateFile(string filePath) - { - if (!File.Exists(filePath)) - { - logger.LogWarning("Journal file {JournalFile} does not exist", filePath); - return false; - } - - var fileInfo = new FileInfo(filePath); - - if (!string.Equals(fileInfo.Name, FileName, StringComparison.InvariantCultureIgnoreCase)) - { - logger.LogWarning("Journal file {name} is not valid"); - return false; - } - - if (fileInfo.Length == 0) - { - logger.LogWarning("Journal file {name} is empty", filePath); - return false; - } - - return true; - } - public async Task HandleFile(string filePath) { if (!FileHelper.ValidateFile(filePath)) @@ -46,6 +21,13 @@ public class StatusService } var file = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + + if (file.Length < 2) + { + logger.LogWarning("File {FilePath} is empty", filePath); + return; + } + var status = await JsonSerializer.DeserializeAsync(file); if (status == null) diff --git a/Pulsar/Utils/JournalJsonConverter.cs b/Pulsar/Utils/JournalJsonConverter.cs new file mode 100644 index 0000000..2465399 --- /dev/null +++ b/Pulsar/Utils/JournalJsonConverter.cs @@ -0,0 +1,301 @@ +using Observatory.Framework.Files.Journal.Combat; +using Observatory.Framework.Files.Journal.Exploration; +using Observatory.Framework.Files.Journal.Odyssey; +using Observatory.Framework.Files.Journal.Other; +using Observatory.Framework.Files.Journal.Powerplay; +using Observatory.Framework.Files.Journal.Startup; +using Observatory.Framework.Files.Journal.StationServices; +using Observatory.Framework.Files.Journal.Trade; +using Observatory.Framework.Files.Journal.Travel; + +namespace Pulsar.Utils; + +using Observatory.Framework.Files.Journal; + + +[Flags] +public enum JournalReaderState +{ + /// + /// Have read the first character of the object + /// + Start, + /// + /// Have read the timestamp. Generally the first property in a journal entry. + /// + Timestamp, + /// + /// have read the event name. Generally the second property in a journal entry. + /// + Event, + /// + /// Have read the last character of the object, the next character should be a newline, whitespace, EOF, or another object. + /// + End, +} + +/// +/// A JournalFile contains a collection of journal entries. +/// Each journal entry is a JSON object, delimited by a newline character. +/// all Journals can be deserialized into a JournalBase object for identification +/// and then deserialized into their respective types. +/// +public class JournalConverter(ILogger logger) : JsonConverter +{ + private JournalReaderState state = JournalReaderState.Start; + public override JournalBase? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + Utf8JsonReader clone = reader; + DateTimeOffset? timestamp = null; + string? eventName = null; + // for debug + int depth = 0; + do + { + depth++; + switch (clone.TokenType) + { + case JsonTokenType.None: + break; + case JsonTokenType.StartObject: + state = JournalReaderState.Start; + break; + case JsonTokenType.EndObject: + state = JournalReaderState.End; + break; + case JsonTokenType.StartArray: + break; + case JsonTokenType.EndArray: + break; + case JsonTokenType.PropertyName: + var propertyName = clone.GetString(); + // if we have not started reading the body, and we have not read the (timestamp or event name) + if ((state & JournalReaderState.Timestamp) == 0 || (state & JournalReaderState.Event) == 0) + { + switch (propertyName) + { + case "timestamp": + clone.Read(); + timestamp = clone.GetDateTimeOffset(); + state = JournalReaderState.Timestamp; + break; + case "event": + clone.Read(); + eventName = clone.GetString(); + state = JournalReaderState.Event; + break; + } + } + + if ((state & JournalReaderState.Event) != 0) + { + // create destination type + return GetDestinationType(ref reader, eventName!); + } + break; + case JsonTokenType.Comment: + continue; + case JsonTokenType.String: + break; + case JsonTokenType.Number: + break; + case JsonTokenType.True: + break; + case JsonTokenType.False: + break; + case JsonTokenType.Null: + break; + default: + throw new ArgumentOutOfRangeException(); + } + + } while (clone.Read()); + + return new() { Timestamp = timestamp!.Value, Event = eventName! }; + + // TODO: handle inf (invalid data) in the journal files + // else if (typeof(TJournal) == typeof(Scan) && json.Contains("\"RotationPeriod\":inf")) + // { + // deserialized = JsonSerializer.Deserialize(json.Replace("\"RotationPeriod\":inf,", "")); + // } + } + + private JournalBase GetDestinationType(ref Utf8JsonReader reader, string eventName) + { + switch (eventName.ToLower()) + { + case "fileheader": + return JsonSerializer.Deserialize(ref reader)!; + case "commander": + return JsonSerializer.Deserialize(ref reader)!; + case "materials": + return JsonSerializer.Deserialize(ref reader)!; + case "rank": + return JsonSerializer.Deserialize(ref reader)!; + case "music": + return JsonSerializer.Deserialize(ref reader)!; + case "cargo": + return JsonSerializer.Deserialize(ref reader)!; + case "loadout": + return JsonSerializer.Deserialize(ref reader)!; + case "missions": + return JsonSerializer.Deserialize(ref reader)!; + case "fsssignaldiscovered": + return JsonSerializer.Deserialize(ref reader)!; + case "reputation": + return JsonSerializer.Deserialize(ref reader)!; + case "loadgame": + return JsonSerializer.Deserialize(ref reader)!; + case "receivetext": + return JsonSerializer.Deserialize(ref reader)!; + case "shiplocker": + return JsonSerializer.Deserialize(ref reader)!; + case "location": + return JsonSerializer.Deserialize(ref reader)!; + case "powerplay": + return JsonSerializer.Deserialize(ref reader)!; + case "reservoirreplenished": + return JsonSerializer.Deserialize(ref reader)!; + case "statistics": + return JsonSerializer.Deserialize(ref reader)!; + case "scan": + return JsonSerializer.Deserialize(ref reader)!; + case "shipyard": + return JsonSerializer.Deserialize(ref reader)!; + case "docked": + return JsonSerializer.Deserialize(ref reader)!; + case "leavebody": + return JsonSerializer.Deserialize(ref reader)!; + case "progress": + return JsonSerializer.Deserialize(ref reader)!; + case "supercruiseexit": + return JsonSerializer.Deserialize(ref reader)!; + case "engineerprogress": + return JsonSerializer.Deserialize(ref reader)!; + case "dockingrequested": + return JsonSerializer.Deserialize(ref reader)!; + case "npccrewpaidwage": + return JsonSerializer.Deserialize(ref reader)!; + case "supercruiseentry": + return JsonSerializer.Deserialize(ref reader)!; + case "dockinggranted": + return JsonSerializer.Deserialize(ref reader)!; + case "startjump": + return JsonSerializer.Deserialize(ref reader)!; + case "fssallbodiesfound": + return JsonSerializer.Deserialize(ref reader)!; + case "fssbodysignals": + return JsonSerializer.Deserialize(ref reader)!; + case "liftoff": + return JsonSerializer.Deserialize(ref reader)!; + case "supercruisedestinationdrop": + return JsonSerializer.Deserialize(ref reader)!; + case "fsdtarget": + return JsonSerializer.Deserialize(ref reader)!; + case "fsdjump": + return JsonSerializer.Deserialize(ref reader)!; + case "codexentry": + return JsonSerializer.Deserialize(ref reader)!; + case "hulldamage": + return JsonSerializer.Deserialize(ref reader)!; + case "materialcollected": + return JsonSerializer.Deserialize(ref reader)!; + case "navroute": + return JsonSerializer.Deserialize(ref reader)!; + case "navrouteclear": + return JsonSerializer.Deserialize(ref reader)!; + case "scanbarycentre": + return JsonSerializer.Deserialize(ref reader)!; + case "jetconeboost": + return JsonSerializer.Deserialize(ref reader)!; + case "shutdown": + return JsonSerializer.Deserialize(ref reader)!; + case "fuelscoop": + return JsonSerializer.Deserialize(ref reader)!; + case "fssdiscoveryscan": + return JsonSerializer.Deserialize(ref reader)!; + case "moduleinfo": + return JsonSerializer.Deserialize(ref reader)!; + case "shiptargeted": + return JsonSerializer.Deserialize(ref reader)!; + case "afmurepairs": + return JsonSerializer.Deserialize(ref reader)!; + case "heatwarning": + return JsonSerializer.Deserialize(ref reader)!; + case "modulebuy": + return JsonSerializer.Deserialize(ref reader)!; + case "buydrones": + return JsonSerializer.Deserialize(ref reader)!; + case "shieldstate": + return JsonSerializer.Deserialize(ref reader)!; + case "buyammo": + return JsonSerializer.Deserialize(ref reader)!; + case "ejectcargo": + return JsonSerializer.Deserialize(ref reader)!; + case "approachbody": + return JsonSerializer.Deserialize(ref reader)!; + case "docksrv": + return JsonSerializer.Deserialize(ref reader)!; + case "touchdown": + return JsonSerializer.Deserialize(ref reader)!; + case "saasignalsfound": + return JsonSerializer.Deserialize(ref reader)!; + case "engineercraft": + return JsonSerializer.Deserialize(ref reader)!; + case "materialtrade": + return JsonSerializer.Deserialize(ref reader)!; + case "repair": + return JsonSerializer.Deserialize(ref reader)!; + case "refuelall": + return JsonSerializer.Deserialize(ref reader)!; + case "storedmodules": + return JsonSerializer.Deserialize(ref reader)!; + case "synthesis": + return JsonSerializer.Deserialize(ref reader)!; + case "scanned": + return JsonSerializer.Deserialize(ref reader)!; + case "sendtext": + return JsonSerializer.Deserialize(ref reader)!; + case "embark": + return JsonSerializer.Deserialize(ref reader)!; + case "multisellexplorationdata": + return JsonSerializer.Deserialize(ref reader)!; + case "backpack": + return JsonSerializer.Deserialize(ref reader)!; + case "modulesell": + return JsonSerializer.Deserialize(ref reader)!; + case "undocked": + return JsonSerializer.Deserialize(ref reader)!; + case "repairall": + return JsonSerializer.Deserialize(ref reader)!; + case "outfitting": + return JsonSerializer.Deserialize(ref reader)!; + case "powerplaysalary": + return JsonSerializer.Deserialize(ref reader)!; + case "redeemvoucher": + return JsonSerializer.Deserialize(ref reader)!; + case "saascancomplete": + return JsonSerializer.Deserialize(ref reader)!; + case "friends": + return JsonSerializer.Deserialize(ref reader)!; + case "launchsrv": + return JsonSerializer.Deserialize(ref reader)!; + case "suitloadout": + return JsonSerializer.Deserialize(ref reader)!; + case "disembark": + return JsonSerializer.Deserialize(ref reader)!; + case "materialdiscovered": + return JsonSerializer.Deserialize(ref reader)!; + case "storedships": + return JsonSerializer.Deserialize(ref reader)!; + default: + logger.LogWarning("Unknown Journal event type {EventName}", eventName); + return JsonSerializer.Deserialize(ref reader)!; + } + } + + public override void Write(Utf8JsonWriter writer, JournalBase value, JsonSerializerOptions options) + { + throw new NotSupportedException(); + } +} \ No newline at end of file diff --git a/Pulsar/Utils/JournalReader.cs b/Pulsar/Utils/JournalReader.cs deleted file mode 100644 index 0185f3c..0000000 --- a/Pulsar/Utils/JournalReader.cs +++ /dev/null @@ -1,70 +0,0 @@ -using Observatory.Framework.Files.Journal; -using Observatory.Framework.Files.Journal.Exploration; - -namespace Pulsar.Utils; - -public static class JournalReader -{ - public static TJournal ObservatoryDeserializer(string json) where TJournal : JournalBase - { - TJournal deserialized; - - if (typeof(TJournal) == typeof(InvalidJson)) - { - InvalidJson invalidJson; - try - { - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); - var eventType = string.Empty; - var timestamp = string.Empty; - - while ((eventType == string.Empty || timestamp == string.Empty) && reader.Read()) - { - if (reader.TokenType == JsonTokenType.PropertyName) - { - if (reader.GetString() == "event") - { - reader.Read(); - eventType = reader.GetString(); - } - else if (reader.GetString() == "timestamp") - { - reader.Read(); - timestamp = reader.GetString(); - } - } - } - - invalidJson = new InvalidJson - { - Event = "InvalidJson", - Timestamp = DateTimeOffset.UnixEpoch, - OriginalEvent = eventType - }; - } - catch - { - invalidJson = new InvalidJson - { - Event = "InvalidJson", - Timestamp = DateTimeOffset.UnixEpoch, - OriginalEvent = "Invalid" - }; - } - - deserialized = (TJournal)Convert.ChangeType(invalidJson, typeof(TJournal)); - } - //Journal potentially had invalid JSON for a brief period in 2017, check for it and remove. - //TODO: Check if this gets handled by InvalidJson now. - else if (typeof(TJournal) == typeof(Scan) && json.Contains("\"RotationPeriod\":inf")) - { - deserialized = JsonSerializer.Deserialize(json.Replace("\"RotationPeriod\":inf,", "")); - } - else - { - deserialized = JsonSerializer.Deserialize(json); - } - - return deserialized; - } -} \ No newline at end of file diff --git a/Pulsar/WebApp/.vscode/settings.json b/Pulsar/WebApp/.vscode/settings.json new file mode 100644 index 0000000..96332ef --- /dev/null +++ b/Pulsar/WebApp/.vscode/settings.json @@ -0,0 +1,10 @@ +{ + "editor.formatOnSave": true, + "editor.formatOnSaveMode": "file", + "[typescript]": { + "editor.defaultFormatter": "biomejs.biome" + }, + "[svelte]": { + "editor.defaultFormatter": "svelte.svelte-vscode" + } +} \ No newline at end of file diff --git a/Pulsar/WebApp/src/lib/JournalLog.svelte b/Pulsar/WebApp/src/lib/JournalLog.svelte index 8b2c616..26e924c 100644 --- a/Pulsar/WebApp/src/lib/JournalLog.svelte +++ b/Pulsar/WebApp/src/lib/JournalLog.svelte @@ -1,146 +1,66 @@
-
-

Journals

-
- -
- {#each data as row} -
-
- Body Name: Farseer Inc -
- Signals: 1 - Base Value: 11111 -
-
- - - - - - - - - - - - - - - - - {#each data as row} - - - - - - - - - - - - {/each} - -
FlagsGenusSpeciesSeenSamplesTypePossible VariantsBase ValueDistance
TestTestTestTestTestTestTestTest500m
-
- {/each} -
+
+

Journals

+
+ +
    + {#each values as value (value.timestamp + value.event)} +
  • + {value.timestamp} + {value.event} + +
  • + {/each} +
- diff --git a/Pulsar/WebApp/src/lib/JournalService.ts b/Pulsar/WebApp/src/lib/JournalService.ts new file mode 100644 index 0000000..ff1202e --- /dev/null +++ b/Pulsar/WebApp/src/lib/JournalService.ts @@ -0,0 +1 @@ +export default class JournalService {} diff --git a/Pulsar/WebApp/src/lib/Status.svelte b/Pulsar/WebApp/src/lib/Status.svelte index a37a20b..0c55fae 100644 --- a/Pulsar/WebApp/src/lib/Status.svelte +++ b/Pulsar/WebApp/src/lib/Status.svelte @@ -1,46 +1,47 @@

Status


-
- {#if $statusStore} - {$statusStore.event} - {(($statusStore.fuel?.fuelMain ?? 0) / 32) * 100}% - {$statusStore?.pips?.join(',')} - {$statusStore?.destination?.name} - {$statusStore.guiFocus} - {$statusStore.cargo} - {:else} - No data :( - {/if} +
+ {#if $statusStore} + Fuel%: {(($statusStore.fuel?.fuelMain ?? 0) / 32) * 100}% + Sys: {$statusStore?.pips?.sys ?? "?"} Eng: {$statusStore?.pips?.eng ?? + "?"} Wep: + {$statusStore?.pips?.wep ?? "?"} + dest?: {$statusStore?.destination?.name} + gui focus: {$statusStore.guiFocus} + cargo: {$statusStore.cargo} + flag1: {$statusStore.flags} + flag2: {$statusStore.flags2} + {:else} + No data :( + {/if}
- diff --git a/Pulsar/WebApp/src/routes/+page.svelte b/Pulsar/WebApp/src/routes/+page.svelte index c19dd30..a2ce11c 100644 --- a/Pulsar/WebApp/src/routes/+page.svelte +++ b/Pulsar/WebApp/src/routes/+page.svelte @@ -1,33 +1,37 @@
-
- -
-
- -
+
+ +
+
+ +
+
+ +
diff --git a/Pulsar/WebApp/src/routes/explorer/Explorer.svelte b/Pulsar/WebApp/src/routes/explorer/Explorer.svelte new file mode 100644 index 0000000..a4bef6d --- /dev/null +++ b/Pulsar/WebApp/src/routes/explorer/Explorer.svelte @@ -0,0 +1,133 @@ + + +
+
+

Explorer

+
+
+ {#each data as row} +
+
+ Body Name: Farseer Inc +
+ Signals: 1 + Base Value: 11111 +
+
+ + + + + + + + + + + + + + + + + {#each data as row} + + + + + + + + + + + + {/each} + +
FlagsGenusSpeciesSeenSamplesTypePossible VariantsBase ValueDistance
TestTestTestTestTestTestTestTest500m
+
+ {/each} +
+
+ + diff --git a/Pulsar/WebApp/src/types/api/Status.ts b/Pulsar/WebApp/src/types/api/Status.ts index 0735038..489d72e 100644 --- a/Pulsar/WebApp/src/types/api/Status.ts +++ b/Pulsar/WebApp/src/types/api/Status.ts @@ -1,17 +1,17 @@ import type Destination from "./Destination"; import type Fuel from "./Fuel"; +import type JournalBase from "./JournalBase"; -export default interface Status { +export default interface Status extends JournalBase { + event: "Status"; flags: number; flags2: number; - pips: number[]; + pips: { eng: number; sys: number; wep: number }; guiFocus: number; fuel: Fuel; cargo: number; legalState: string; balance: number; destination: Destination; - timestamp: Date; - event: string; FireGroup: number; }