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(); } }