From 256ebb179ef34e9ecb1fa320dd5c5543b33dd50b Mon Sep 17 00:00:00 2001 From: Ben Parsons <9parsonsb@gmail.com> Date: Sun, 14 Apr 2024 21:51:56 +1000 Subject: [PATCH] Rework Journal File Reading Remove Explorer Remove Plugin Architecture --- Botanist/BioPlanetDetail.cs | 8 + Botanist/BioSampleDetail.cs | 7 + Botanist/BodyAddress.cs | 33 + Botanist/Botanist.cs | 126 +--- Botanist/BotanistGrid.cs | 10 + Explorer/CriteriaLoadException.cs | 13 - Explorer/CustomCriteriaManager.cs | 382 ----------- Explorer/DefaultCriteria.cs | 389 ------------ Explorer/Explorer.cs | 304 --------- Explorer/Explorer.csproj | 17 - Explorer/Explorer.sln | 25 - Explorer/ExplorerSettings.cs | 78 --- Explorer/ExplorerUIResults.cs | 12 - Explorer/Worker.cs | 121 ---- ObservatoryFramework/EventArgs.cs | 39 +- .../FleetCarrier/CarrierDecommission.cs | 8 +- .../FleetCarrier/CarrierJumpRequest.cs | 11 +- .../Files/Journal/JournalBase.cs | 317 +++++++-- .../Files/Journal/JournalUtilities.cs | 32 +- .../Files/Journal/Travel/Docked.cs | 35 +- .../Files/Journal/Travel/FSDJump.cs | 5 +- .../Files/Journal/Travel/Location.cs | 5 +- .../Files/ParameterTypes/CurrentGoal.cs | 10 +- .../Files/ParameterTypes/MaterialTrader.cs | 8 +- ObservatoryFramework/IObservatoryCore.cs | 81 +++ ObservatoryFramework/Interfaces.cs | 153 +---- .../ObservatoryFramework.csproj | 1 + Pulsar.sln | 6 - Pulsar/Assets/EOCIcon-Presized.ico | Bin 215434 -> 0 bytes Pulsar/Global.Usings.cs | 6 + Pulsar/LoggingUtils.cs | 2 - Pulsar/PluginManagement/PlaceholderPlugin.cs | 29 - Pulsar/PluginManagement/PluginCore.cs | 95 --- Pulsar/PluginManagement/PluginEventHandler.cs | 192 ------ Pulsar/PluginManagement/PluginManager.cs | 329 ---------- Pulsar/Program.cs | 25 +- Pulsar/Pulsar.csproj | 1 + Pulsar/Utils/CollectionExtesions.cs | 14 + Pulsar/Utils/ErrorReporter.cs | 8 +- Pulsar/Utils/HttpClient.cs | 8 +- Pulsar/Utils/JournalReader.cs | 116 ++-- Pulsar/Utils/LogMonitor.cs | 601 +++++++++--------- 42 files changed, 855 insertions(+), 2807 deletions(-) create mode 100644 Botanist/BioPlanetDetail.cs create mode 100644 Botanist/BioSampleDetail.cs create mode 100644 Botanist/BodyAddress.cs create mode 100644 Botanist/BotanistGrid.cs delete mode 100644 Explorer/CriteriaLoadException.cs delete mode 100644 Explorer/CustomCriteriaManager.cs delete mode 100644 Explorer/DefaultCriteria.cs delete mode 100644 Explorer/Explorer.cs delete mode 100644 Explorer/Explorer.csproj delete mode 100644 Explorer/Explorer.sln delete mode 100644 Explorer/ExplorerSettings.cs delete mode 100644 Explorer/ExplorerUIResults.cs delete mode 100644 Explorer/Worker.cs create mode 100644 ObservatoryFramework/IObservatoryCore.cs delete mode 100644 Pulsar/Assets/EOCIcon-Presized.ico create mode 100644 Pulsar/Global.Usings.cs delete mode 100644 Pulsar/PluginManagement/PlaceholderPlugin.cs delete mode 100644 Pulsar/PluginManagement/PluginCore.cs delete mode 100644 Pulsar/PluginManagement/PluginEventHandler.cs delete mode 100644 Pulsar/PluginManagement/PluginManager.cs create mode 100644 Pulsar/Utils/CollectionExtesions.cs diff --git a/Botanist/BioPlanetDetail.cs b/Botanist/BioPlanetDetail.cs new file mode 100644 index 0000000..65b2c3e --- /dev/null +++ b/Botanist/BioPlanetDetail.cs @@ -0,0 +1,8 @@ +namespace Botanist; + +class BioPlanetDetail +{ + public string BodyName { get; set; } + public int BioTotal { get; set; } + public Dictionary SpeciesFound { get; set; } +} \ No newline at end of file diff --git a/Botanist/BioSampleDetail.cs b/Botanist/BioSampleDetail.cs new file mode 100644 index 0000000..89b56d5 --- /dev/null +++ b/Botanist/BioSampleDetail.cs @@ -0,0 +1,7 @@ +namespace Botanist; + +class BioSampleDetail +{ + public string Genus { get; set; } + public bool Analysed { get; set; } +} \ No newline at end of file diff --git a/Botanist/BodyAddress.cs b/Botanist/BodyAddress.cs new file mode 100644 index 0000000..7ca92df --- /dev/null +++ b/Botanist/BodyAddress.cs @@ -0,0 +1,33 @@ +namespace Botanist; + +class BodyAddress +{ + public ulong SystemAddress { get; set; } + public int BodyID { get; set; } + + public override bool Equals(object obj) + { + // We want value equality here. + + // + // See the full list of guidelines at + // http://go.microsoft.com/fwlink/?LinkID=85237 + // and also the guidance for operator== at + // http://go.microsoft.com/fwlink/?LinkId=85238 + // + + if (obj == null || GetType() != obj.GetType()) + { + return false; + } + + var other = (BodyAddress)obj; + return other.SystemAddress == SystemAddress + && other.BodyID == BodyID; + } + + public override int GetHashCode() + { + return HashCode.Combine(SystemAddress, BodyID); + } +} \ No newline at end of file diff --git a/Botanist/Botanist.cs b/Botanist/Botanist.cs index bdac10d..c70de78 100644 --- a/Botanist/Botanist.cs +++ b/Botanist/Botanist.cs @@ -1,5 +1,4 @@ using System.Collections.ObjectModel; -using System.Reflection; using Observatory.Framework; using Observatory.Framework.Files.Journal; using Observatory.Framework.Files.Journal.Exploration; @@ -19,7 +18,7 @@ public class Botanist : IObservatoryWorker // To make this journal locale agnostic, use the genus identifier and map to English names used in notifications. // Note: Values here are also used in the lookup for colony distance, so we also use this to resolve misspellings and Frontier bugs. - private readonly Dictionary EnglishGenusByIdentifier = new() + public static readonly IReadOnlyDictionary EnglishGenusByIdentifier = new Dictionary { { "$Codex_Ent_Aleoids_Genus_Name;", "Aleoida" }, { "$Codex_Ent_Bacterial_Genus_Name;", "Bacterium" }, @@ -51,7 +50,7 @@ public class Botanist : IObservatoryWorker }; // Note: Some Horizons bios may be missing, but they'll get localized genus name and default colony distance - private readonly Dictionary ColonyDistancesByGenus = new() + public static readonly IReadOnlyDictionary ColonyDistancesByGenus = new Dictionary() { { "Aleoida", 150 }, { "Bacterium", 500 }, @@ -99,7 +98,7 @@ public class Botanist : IObservatoryWorker public object Settings { get => botanistSettings; - set { botanistSettings = (BotanistSettings)value; } + set => botanistSettings = (BotanistSettings)value; } public void JournalEvent(TJournal journal) where TJournal : JournalBase @@ -216,8 +215,6 @@ public class Botanist : IObservatoryWorker break; } } - - UpdateUIGrid(); } break; case LeaveBody: @@ -259,121 +256,4 @@ public class Botanist : IObservatoryWorker Core = observatoryCore; } - - public void LogMonitorStateChanged(LogMonitorStateChangedEventArgs args) - { - if (LogMonitorStateChangedEventArgs.IsBatchRead(args.NewState)) - { - // Beginning a batch read. Clear grid. - Core.SetGridItems(this, - [ - typeof(BotanistGrid).GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(p => p.Name) - .ToDictionary(p => p, p => string.Empty) - ]); - } - else if (LogMonitorStateChangedEventArgs.IsBatchRead(args.PreviousState)) - { - // Batch read is complete. Show data. - UpdateUIGrid(); - } - } - - private void UpdateUIGrid() - { - // Suppress repainting the entire contents of the grid on every ScanOrganic record we read. - if (Core.IsLogMonitorBatchReading) return; - - Core.SetGridItems(this, [ - typeof(BotanistGrid).GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(p => p.Name) - .ToDictionary(p => p, p => string.Empty) - ]); - foreach (var bioPlanet in BioPlanets.Values) - { - if (bioPlanet.SpeciesFound.Count == 0) - { - var planetRow = new BotanistGrid - { - Body = bioPlanet.BodyName, - BioTotal = bioPlanet.BioTotal.ToString(), - Species = "(NO SAMPLES TAKEN)", - Analysed = string.Empty, - ColonyDistance = string.Empty, - }; - Core.AddGridItem(this, planetRow); - } - - var firstRow = true; - foreach (var entry in bioPlanet.SpeciesFound) - { - var colonyDistance = - ColonyDistancesByGenus.GetValueOrDefault(entry.Value.Genus ?? "", DEFAULT_COLONY_DISTANCE); - var speciesRow = new BotanistGrid - { - Body = firstRow ? bioPlanet.BodyName : string.Empty, - BioTotal = firstRow ? bioPlanet.BioTotal.ToString() : string.Empty, - Species = entry.Key, - Analysed = entry.Value.Analysed ? "✓" : "", - ColonyDistance = $"{colonyDistance}m", - }; - Core.AddGridItem(this, speciesRow); - firstRow = false; - } - } - } -} - -class BodyAddress -{ - public ulong SystemAddress { get; set; } - public int BodyID { get; set; } - - public override bool Equals(object obj) - { - // We want value equality here. - - // - // See the full list of guidelines at - // http://go.microsoft.com/fwlink/?LinkID=85237 - // and also the guidance for operator== at - // http://go.microsoft.com/fwlink/?LinkId=85238 - // - - if (obj == null || GetType() != obj.GetType()) - { - return false; - } - - var other = (BodyAddress)obj; - return other.SystemAddress == SystemAddress - && other.BodyID == BodyID; - } - - public override int GetHashCode() - { - return HashCode.Combine(SystemAddress, BodyID); - } -} - -class BioPlanetDetail -{ - public string BodyName { get; set; } - public int BioTotal { get; set; } - public Dictionary SpeciesFound { get; set; } -} - -class BioSampleDetail -{ - public string Genus { get; set; } - public bool Analysed { get; set; } -} - -public class BotanistGrid -{ - public string Body { get; set; } - public string BioTotal { get; set; } - public string Species { get; set; } - public string Analysed { get; set; } - public string ColonyDistance { get; set; } } \ No newline at end of file diff --git a/Botanist/BotanistGrid.cs b/Botanist/BotanistGrid.cs new file mode 100644 index 0000000..652eb8e --- /dev/null +++ b/Botanist/BotanistGrid.cs @@ -0,0 +1,10 @@ +namespace Botanist; + +public class BotanistGrid +{ + public string Body { get; set; } + public string BioTotal { get; set; } + public string Species { get; set; } + public string Analysed { get; set; } + public string ColonyDistance { get; set; } +} \ No newline at end of file diff --git a/Explorer/CriteriaLoadException.cs b/Explorer/CriteriaLoadException.cs deleted file mode 100644 index 9b286c2..0000000 --- a/Explorer/CriteriaLoadException.cs +++ /dev/null @@ -1,13 +0,0 @@ -namespace Explorer; - -internal class CriteriaLoadException : Exception -{ - public CriteriaLoadException(string message, string script) - { - Message = message; - OriginalScript = script; - } - - new public readonly string Message; - public readonly string OriginalScript; -} \ No newline at end of file diff --git a/Explorer/CustomCriteriaManager.cs b/Explorer/CustomCriteriaManager.cs deleted file mode 100644 index df54763..0000000 --- a/Explorer/CustomCriteriaManager.cs +++ /dev/null @@ -1,382 +0,0 @@ -using System.Text; -using NLua; -using NLua.Exceptions; -using Observatory.Framework.Files.Journal.Exploration; - -namespace Explorer; - -internal class CustomCriteriaManager -{ - private Lua LuaState; - private Dictionary CriteriaFunctions; - private Dictionary CriteriaWithErrors = new(); - Action ErrorLogger; - private uint ScanCount; - - public CustomCriteriaManager(Action errorLogger) - { - ErrorLogger = errorLogger; - CriteriaFunctions = new(); - ScanCount = 0; - } - - public void RefreshCriteria(string criteriaPath) - { - LuaState = new(); - LuaState.State.Encoding = Encoding.UTF8; - LuaState.LoadCLRPackage(); - - #region Iterators - - // Empty function for nil iterators - LuaState.DoString("function nil_iterator() end"); - - //Materials and AtmosphereComposition - LuaState.DoString(@" - function materials (material_list) - if material_list then - local i = 0 - local count = material_list.Count - return function () - i = i + 1 - if i <= count then - return { name = material_list[i - 1].Name, percent = material_list[i - 1].Percent } - end - end - else - return nil_iterator - end - end"); - - //Rings - internal filterable iterator - LuaState.DoString(@" - function _ringsFiltered (ring_list, filter_by) - if ring_list then - local i = 0 - local count = ring_list.Count - return function () - i = i + 1 - while i <= count do - local ring = ring_list[i - 1] - if (filter_by == nil or string.find(ring.Name, filter_by)) then - return { name = ring.Name, ringclass = ring.RingClass, massmt = ring.MassMT, innerrad = ring.InnerRad, outerrad = ring.OuterRad } - else - i = i + 1 - end - end - end - else - return nil_iterator - end - end"); - - //Rings - internal filterable hasX check - LuaState.DoString(@" - function _hasRingsFiltered (ring_list, filter_by) - if ring_list then - local i = 0 - local count = ring_list.Count - while i < count do - if string.find(ring_list[i].Name, filter_by) then - return true - end - i = i + 1 - end - end - return false - end"); - - //Rings - iterate all - nil filter - LuaState.DoString(@" - function rings (ring_list) - return _ringsFiltered(ring_list, nil) - end"); - - //Rings - iterate proper rings only - LuaState.DoString(@" - function ringsOnly (ring_list) - return _ringsFiltered(ring_list, 'Ring') - end"); - - //Rings - iterate belts only - LuaState.DoString(@" - function beltsOnly (ring_list) - return _ringsFiltered(ring_list, 'Belt') - end"); - - //Bodies in system - LuaState.DoString(@" - function bodies (system_list) - if system_list then - local i = 0 - local count = system_list.Count - return function () - i = i + 1 - if i <= count then - return system_list[i - 1] - end - end - else - return nil_iterator - end - end"); - - //Parent bodies - LuaState.DoString(@" - function allparents (parent_list) - if parent_list then - local i = 0 - local count - if parent_list then count = parent_list.Count else count = 0 end - return function () - i = i + 1 - if i <= count then - return { parenttype = parent_list[i - 1].ParentType, body = parent_list[i - 1].Body, scan = parent_list[i - 1].Scan } - end - end - else - return nil_iterator - end - end"); - - #endregion - - #region Convenience Functions - - //Rings - has > 0 belts - LuaState.DoString(@" - function hasBelts (ring_list) - return _hasRingsFiltered(ring_list, 'Belt') - end"); - - //Rings - has > 0 proper rings - LuaState.DoString(@" - function hasRings (ring_list) - return _hasRingsFiltered(ring_list, 'Ring') - end"); - - LuaState.DoString(@" - function isStar (scan) - return scan.StarType and scan.StarType ~= '' - end"); - - LuaState.DoString(@" - function isPlanet (scan) - return scan.PlanetClass ~= nil - end"); - - LuaState.DoString(@" - function hasAtmosphere (scan) - return scan.AtmosphereComposition ~= nil - end"); - - LuaState.DoString(@" - function hasLandableAtmosphere (scan) - return scan.Landable and scan.AtmosphereComposition ~= nil - end"); - - #endregion - - CriteriaFunctions.Clear(); - CriteriaWithErrors.Clear(); - var criteria = File.Exists(criteriaPath) ? File.ReadAllLines(criteriaPath) : Array.Empty(); - StringBuilder script = new(); - - try - { - for (var i = 0; i < criteria.Length; i++) - { - if (criteria[i].Trim().StartsWith("::")) - { - var scriptDescription = criteria[i].Trim().Replace("::", string.Empty); - if (scriptDescription.ToLower() == "criteria" || scriptDescription.ToLower().StartsWith("criteria=")) - { - var functionName = $"Criteria{i}"; - script.AppendLine($"function {functionName} (scan, parents, system, biosignals, geosignals)"); - i++; - do - { - if (i >= criteria.Length) - throw new Exception("Unterminated multi-line criteria.\r\nAre you missing an ::End::?"); - - script.AppendLine(criteria[i]); - i++; - } while (!criteria[i].Trim().ToLower().StartsWith("::end::")); - script.AppendLine("end"); - - LuaState.DoString(script.ToString()); - CriteriaFunctions.Add(GetUniqueDescription(functionName, scriptDescription), LuaState[functionName] as LuaFunction); - script.Clear(); - } - else if (scriptDescription.ToLower() == "global") - { - i++; - do - { - script.AppendLine(criteria[i]); - i++; - } while (!criteria[i].Trim().ToLower().StartsWith("::end::")); - LuaState.DoString(script.ToString()); - script.Clear(); - } - else - { - i++; - - var functionName = $"Criteria{i}"; - - script.AppendLine($"function {functionName} (scan, parents, system, biosignals, geosignals)"); - script.AppendLine($" local result = {criteria[i]}"); - script.AppendLine(" local detail = ''"); - - if (criteria.Length > i + 1 && criteria[i + 1].Trim().ToLower() == "::detail::") - { - i++; i++; - // Gate detail evaluation on result to allow safe use of criteria-checked values in detail string. - script.AppendLine(" if result then"); - script.AppendLine($" detail = {criteria[i]}"); - script.AppendLine(" end"); - } - - script.AppendLine($" return result, '{scriptDescription}', detail"); - script.AppendLine("end"); - - LuaState.DoString(script.ToString()); - CriteriaFunctions.Add(GetUniqueDescription(functionName, scriptDescription), LuaState[functionName] as LuaFunction); - script.Clear(); - } - } - } - } - catch (Exception e) - { - var originalScript = script.ToString().Trim(); - - originalScript = originalScript.Remove(originalScript.LastIndexOf(Environment.NewLine)); - originalScript = originalScript[(originalScript.IndexOf(Environment.NewLine) + Environment.NewLine.Length)..]; - originalScript = originalScript.Replace('\t', ' '); - - StringBuilder errorDetail = new(); - errorDetail.AppendLine("Error Reading Custom Criteria File:") - .AppendLine(originalScript) - .AppendLine("To correct this problem, make changes to the Lua source file, save it and either re-run read-all or scan another body. It will be automatically reloaded."); ErrorLogger(e, errorDetail.ToString()); - CriteriaFunctions.Clear(); // Don't use partial parse. - throw new CriteriaLoadException(e.Message, originalScript); - } - } - - public List<(string, string, bool)> CheckInterest(Scan scan, Dictionary> scanHistory, Dictionary> signalHistory, ExplorerSettings settings) - { - List<(string, string, bool)> results = new(); - ScanCount++; - - foreach (var criteriaFunction in CriteriaFunctions) - { - // Skip criteria which have previously thrown an error. We can't remove them from the dictionary while iterating it. - if (CriteriaWithErrors.ContainsKey(criteriaFunction.Key)) continue; - - var scanList = scanHistory[scan.SystemAddress].Values.ToList(); - - int bioSignals; - int geoSignals; - - if (signalHistory.ContainsKey(scan.SystemAddress) && signalHistory[scan.SystemAddress].ContainsKey(scan.BodyID)) - { - bioSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Biological;").Select(s => s.Count).FirstOrDefault(); - geoSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Geological;").Select(s => s.Count).FirstOrDefault(); - } - else - { - bioSignals = 0; - geoSignals = 0; - } - - - List parents; - - if (scan.Parent != null) - { - parents = new(); - foreach (var parent in scan.Parent) - { - var parentScan = scanList.Where(s => s.BodyID == parent.Body); - - parents.Add(new Parent - { - ParentType = parent.ParentType.ToString(), - Body = parent.Body, - Scan = parentScan.Any() ? parentScan.First() : null - }); - } - } - else - { - parents = null; - } - - try - { - var result = criteriaFunction.Value.Call(scan, parents, scanList, bioSignals, geoSignals); - if (result.Length == 3 && ((bool?)result[0]).GetValueOrDefault(false)) - { - results.Add((result[1].ToString(), result[2].ToString(), false)); - } - else if (result.Length == 2) - { - results.Add((result[0].ToString(), result[1].ToString(), false)); - } - } - catch (LuaScriptException e) - { - results.Add((e.Message, scan.Json, false)); - - StringBuilder errorDetail = new(); - errorDetail.AppendLine($"while processing custom criteria '{criteriaFunction.Key}' on scan:") - .AppendLine(scan.Json) - .AppendLine("To correct this problem, make changes to the Lua source file, save it and either re-run read-all or scan another body. It will be automatically reloaded."); - ErrorLogger(e, errorDetail.ToString()); - CriteriaWithErrors.Add(criteriaFunction.Key, e.Message + Environment.NewLine + errorDetail); - } - } - - // Remove any erroring criteria. They will be repopulated next time the file is parsed. - if (CriteriaWithErrors.Count > 0) - { - foreach (var criteriaKey in CriteriaWithErrors.Keys) - { - if (CriteriaFunctions.ContainsKey(criteriaKey)) CriteriaFunctions.Remove(criteriaKey); - } - } - - if (ScanCount > 99) - { - ScanCount = 0; - LuaGC(); - } - - return results; - } - - private string GetUniqueDescription(string functionName, string scriptDescription) - { - var uniqueDescription = functionName; - if (scriptDescription.ToLower().StartsWith("criteria=")) - { - uniqueDescription += scriptDescription.Replace("criteria=", "=", StringComparison.CurrentCultureIgnoreCase); - } - return uniqueDescription; - } - - private void LuaGC() - { - LuaState?.DoString("collectgarbage()"); - } - - internal class Parent - { - public string ParentType; - public int Body; - public Scan Scan; - } - -} \ No newline at end of file diff --git a/Explorer/DefaultCriteria.cs b/Explorer/DefaultCriteria.cs deleted file mode 100644 index 3a6789d..0000000 --- a/Explorer/DefaultCriteria.cs +++ /dev/null @@ -1,389 +0,0 @@ -using System.Globalization; -using System.Text; -using Observatory.Framework.Files.Journal.Exploration; -using Observatory.Framework.Files.ParameterTypes; - -namespace Explorer; - -internal static class DefaultCriteria -{ - public static List<(string Description, string Detail, bool SystemWide)> CheckInterest(Scan scan, Dictionary> scanHistory, Dictionary> signalHistory, ExplorerSettings settings) - { - List<(string, string, bool)> results = new(); - var textInfo = new CultureInfo("en-US", false).TextInfo; - - var isRing = scan.BodyName.Contains("Ring"); - -#if DEBUG - // results.Add("Test Scan Event", "Test Detail"); -#endif - - #region Landable Checks - if (scan.Landable) - { - if (settings.LandableTerraformable && scan.TerraformState?.Length > 0) - { - results.Add($"Landable and {scan.TerraformState}"); - } - - if (settings.LandableRing && scan.Rings?.Count > 0) - { - results.Add("Ringed Landable Body"); - } - - if (settings.LandableAtmosphere && scan.Atmosphere.Length > 0) - { - results.Add("Landable with Atmosphere", textInfo.ToTitleCase(scan.Atmosphere)); - } - - if (settings.LandableHighG && scan.SurfaceGravity > 29.4) - { - results.Add("Landable with High Gravity", $"Surface gravity: {scan.SurfaceGravity / 9.81:0.00}g"); - } - - if (settings.LandableLarge && scan.Radius > 18000000) - { - results.Add("Landable Large Planet", $"Radius: {scan.Radius / 1000:0}km"); - } - } - #endregion - - #region Value Checks - if (settings.HighValueMappable) - { - IList HighValueNonTerraformablePlanetClasses = new[] { - "Earthlike body", - "Ammonia world", - "Water world", - }; - - if (HighValueNonTerraformablePlanetClasses.Contains(scan.PlanetClass) || scan.TerraformState?.Length > 0) - { - var info = new StringBuilder(); - - if (!scan.WasMapped) - { - if (!scan.WasDiscovered) - info.Append("Undiscovered "); - else - info.Append("Unmapped "); - } - - if (scan.TerraformState?.Length > 0) - info.Append("Terraformable "); - - results.Add("High-Value Body", $"{info}{textInfo.ToTitleCase(scan.PlanetClass)}, {scan.DistanceFromArrivalLS:N0}Ls"); - } - } - #endregion - - #region Parent Relative Checks - - if (scan.SystemAddress != 0 && scan.SemiMajorAxis != 0 && - scanHistory[scan.SystemAddress].ContainsKey(scan.Parent[0].Body)) - { - var parent = scanHistory[scan.SystemAddress][scan.Parent[0].Body]; - - if (settings.CloseOrbit && !isRing && parent.Radius * 3 > scan.SemiMajorAxis) - { - results.Add("Close Orbit", $"Orbital Radius: {scan.SemiMajorAxis / 1000:N0}km, Parent Radius: {parent.Radius / 1000:N0}km"); - } - - if (settings.ShepherdMoon && !isRing && parent.Rings?.Any() == true && parent.Rings.Last().OuterRad > scan.SemiMajorAxis && !parent.Rings.Last().Name.Contains("Belt")) - { - results.Add("Shepherd Moon", $"Orbit: {scan.SemiMajorAxis / 1000:N0}km, Ring Radius: {parent.Rings.Last().OuterRad / 1000:N0}km"); - } - - if (settings.CloseRing && parent.Rings?.Count > 0) - { - foreach (var ring in parent.Rings) - { - var separation = Math.Min(Math.Abs(scan.SemiMajorAxis - ring.OuterRad), Math.Abs(ring.InnerRad - scan.SemiMajorAxis)); - if (separation < scan.Radius * 10) - { - var ringTypeName = ring.Name.Contains("Belt") ? "Belt" : "Ring"; - var isLandable = scan.Landable ? ", Landable" : ""; - results.Add($"Close {ringTypeName} Proximity", - $"Orbit: {scan.SemiMajorAxis / 1000:N0}km, Radius: {scan.Radius / 1000:N0}km, Distance from {ringTypeName.ToLower()}: {separation / 1000:N0}km{isLandable}"); - } - } - } - } - - #endregion - - if (settings.DiverseLife && signalHistory.ContainsKey(scan.SystemAddress) && signalHistory[scan.SystemAddress].ContainsKey(scan.BodyID)) - { - var bioSignals = signalHistory[scan.SystemAddress][scan.BodyID].Signals.Where(s => s.Type == "$SAA_SignalType_Biological;"); - - if (bioSignals.Count() > 0 && bioSignals.First().Count > 7) - { - results.Add("Diverse Life", $"Biological Signals: {bioSignals.First().Count}"); - } - } - - if (settings.WideRing && scan.Rings?.Count > 0) - { - foreach (var ring in scan.Rings.Where(r => !r.Name.Contains("Belt"))) - { - var ringWidth = ring.OuterRad - ring.InnerRad; - if (ringWidth > scan.Radius * 5) - { - var ringName = ring.Name.Replace(scan.BodyName, "").Trim(); - results.Add("Wide Ring", $"{ringName}: Width: {ringWidth / 299792458:N2}Ls / {ringWidth / 1000:N0}km, Parent Radius: {scan.Radius / 1000:N0}km"); - } - } - } - - if (settings.SmallObject && scan.StarType == null && scan.PlanetClass != null && scan.PlanetClass != "Barycentre" && scan.Radius < 300000) - { - results.Add("Small Object", $"Radius: {scan.Radius / 1000:N0}km"); - } - - if (settings.HighEccentricity && scan.Eccentricity > 0.9) - { - results.Add("Highly Eccentric Orbit", $"Eccentricity: {Math.Round(scan.Eccentricity, 4)}"); - } - - if (settings.Nested && !isRing && scan.Parent?.Count > 1 && scan.Parent[0].ParentType == ParentType.Planet && scan.Parent[1].ParentType == ParentType.Planet) - { - results.Add("Nested Moon"); - } - - if (settings.FastRotation && scan.RotationPeriod != 0 && !scan.TidalLock && Math.Abs(scan.RotationPeriod) < 28800 && !isRing && string.IsNullOrEmpty(scan.StarType)) - { - results.Add("Non-locked Body with Fast Rotation", $"Period: {scan.RotationPeriod/3600:N1} hours"); - } - - if (settings.FastOrbit && scan.OrbitalPeriod != 0 && Math.Abs(scan.OrbitalPeriod) < 28800 && !isRing) - { - results.Add("Fast Orbit", $"Orbital Period: {Math.Abs(scan.OrbitalPeriod / 3600):N1} hours"); - } - - // Close binary pair - if ((settings.CloseBinary || settings.CollidingBinary) && scan.Parent?[0].ParentType == ParentType.Null && scan.Radius / scan.SemiMajorAxis > 0.4) - { - var binaryPartner = scanHistory[scan.SystemAddress].Where(priorScan => priorScan.Value.Parent?[0].Body == scan.Parent?[0].Body && scan.BodyID != priorScan.Key); - - if (binaryPartner.Count() == 1) - { - if (binaryPartner.First().Value.Radius / binaryPartner.First().Value.SemiMajorAxis > 0.4) - { - if (settings.CollidingBinary && binaryPartner.First().Value.Radius + scan.Radius >= binaryPartner.First().Value.SemiMajorAxis * (1 - binaryPartner.First().Value.Eccentricity) + scan.SemiMajorAxis * (1 - scan.Eccentricity)) - { - results.Add("COLLIDING binary", $"Orbit: {Math.Truncate((double)scan.SemiMajorAxis / 1000):N0}km, Radius: {Math.Truncate((double)scan.Radius / 1000):N0}km, Partner: {binaryPartner.First().Value.BodyName}"); - } - else if (settings.CloseBinary) - { - results.Add("Close binary relative to body size", $"Orbit: {Math.Truncate((double)scan.SemiMajorAxis / 1000):N0}km, Radius: {Math.Truncate((double)scan.Radius / 1000):N0}km, Partner: {binaryPartner.First().Value.BodyName}"); - } - } - } - } - - if (settings.GoodFSDBody && scan.Landable) - { - List boostMaterials = new() - { - "Carbon", - "Germanium", - "Arsenic", - "Niobium", - "Yttrium", - "Polonium" - }; - - if (boostMaterials.RemoveMatchedMaterials(scan) == 1) - { - results.Add("5 of 6 Premium Boost Materials", $"Missing material: {boostMaterials[0]}"); - } - } - - if ((settings.GreenSystem || settings.GoldSystem) && scan.Materials != null) - { - List boostMaterials = new() - { - "Carbon", - "Germanium", - "Arsenic", - "Niobium", - "Yttrium", - "Polonium" - }; - - List allSurfaceMaterials = new() - { - "Antimony", "Arsenic", "Cadmium", "Carbon", - "Chromium", "Germanium", "Iron", "Manganese", - "Mercury", "Molybdenum", "Nickel", "Niobium", - "Phosphorus", "Polonium", "Ruthenium", "Selenium", - "Sulphur", "Technetium", "Tellurium", "Tin", - "Tungsten", "Vanadium", "Yttrium", "Zinc", - "Zirconium" - }; - - var systemBodies = scanHistory[scan.SystemAddress]; - - var notifyGreen = false; - var notifyGold = false; - - foreach (var body in systemBodies.Values) - { - - // If we enter either check and the count is already zero then a - // previous body in system triggered the check, suppress notification. - if (settings.GreenSystem && body.Materials != null) - { - if (boostMaterials.Count == 0) - notifyGreen = false; - else if (boostMaterials.RemoveMatchedMaterials(body) == 0) - notifyGreen = true; - } - - if (settings.GoldSystem && body.Materials != null) - { - if (allSurfaceMaterials.Count == 0) - notifyGold = false; - else if (allSurfaceMaterials.RemoveMatchedMaterials(body) == 0) - notifyGold = true; - } - } - - if (notifyGreen) - results.Add("All Premium Boost Materials In System", string.Empty, true); - - if (notifyGold) - results.Add("All Surface Materials In System", string.Empty, true); - } - - if (settings.UncommonSecondary && scan.BodyID > 0 && !string.IsNullOrWhiteSpace(scan.StarType) && scan.DistanceFromArrivalLS > 10) - { - var excludeTypes = new List { "O", "B", "A", "F", "G", "K", "M", "L", "T", "Y", "TTS" }; - if (!excludeTypes.Contains(scan.StarType.ToUpper())) - { - results.Add("Uncommon Secondary Star Type", $"{GetUncommonStarTypeName(scan.StarType)}, Distance: {scan.DistanceFromArrivalLS:N0}Ls"); - } - } - - return results; - } - - private static string GetUncommonStarTypeName(string starType) - { - string name; - - switch (starType.ToLower()) - { - case "b_bluewhitesupergiant": - name = "B Blue-White Supergiant"; - break; - case "a_bluewhitesupergiant": - name = "A Blue-White Supergiant"; - break; - case "f_whitesupergiant": - name = "F White Supergiant"; - break; - case "g_whitesupergiant": - name = "G White Supergiant"; - break; - case "k_orangegiant": - name = "K Orange Giant"; - break; - case "m_redgiant": - name = "M Red Giant"; - break; - case "m_redsupergiant": - name = "M Red Supergiant"; - break; - case "aebe": - name = "Herbig Ae/Be"; - break; - case "w": - case "wn": - case "wnc": - case "wc": - case "wo": - name = "Wolf-Rayet"; - break; - case "c": - case "cs": - case "cn": - case "cj": - case "ch": - case "chd": - name = "Carbon Star"; - break; - case "s": - name = "S-Type Star"; - break; - case "ms": - name = "MS-Type Star"; - break; - case "d": - case "da": - case "dab": - case "dao": - case "daz": - case "dav": - case "db": - case "dbz": - case "dbv": - case "do": - case "dov": - case "dq": - case "dc": - case "dcv": - case "dx": - name = "White Dwarf"; - break; - case "n": - name = "Neutron Star"; - break; - case "h": - name = "Black Hole"; - break; - case "supermassiveblackhole": - name = "Supermassive Black Hole"; - break; - case "x": - name = "Exotic Star"; - break; - case "rogueplanet": - name = "Rogue Planet"; - break; - case "tts": - case "t": - name = "T Tauri Type"; - break; - default: - name = starType + "-Type Star"; - break; - } - - return name; - } - - /// - /// Removes materials from the list if found on the specified body. - /// - /// - /// - /// Count of materials remaining in list. - private static int RemoveMatchedMaterials(this List materials, Scan body) - { - foreach (var material in body.Materials) - { - var matchedMaterial = materials.Find(mat => mat.Equals(material.Name, StringComparison.OrdinalIgnoreCase)); - if (matchedMaterial != null) - { - materials.Remove(matchedMaterial); - } - } - return materials.Count; - } - - private static void Add(this List<(string, string, bool)> results, string description, string detail = "", bool systemWide = false) - { - results.Add((description, detail, systemWide)); - } -} \ No newline at end of file diff --git a/Explorer/Explorer.cs b/Explorer/Explorer.cs deleted file mode 100644 index 2dc7f7c..0000000 --- a/Explorer/Explorer.cs +++ /dev/null @@ -1,304 +0,0 @@ -using System.Reflection; -using System.Security; -using System.Text; -using Observatory.Framework; -using Observatory.Framework.Files.Journal.Exploration; - -namespace Explorer; - -internal class Explorer -{ - private IObservatoryCore ObservatoryCore; - private ExplorerWorker ExplorerWorker; - private Dictionary> SystemBodyHistory; - private Dictionary> BodySignalHistory; - private Dictionary> BarycentreHistory; - private CustomCriteriaManager CustomCriteriaManager; - private DateTime CriteriaLastModified; - private string currentSystem = string.Empty; - - internal Explorer(ExplorerWorker explorerWorker, IObservatoryCore core) - { - SystemBodyHistory = new(); - BodySignalHistory = new(); - BarycentreHistory = new(); - ExplorerWorker = explorerWorker; - ObservatoryCore = core; - CustomCriteriaManager = new(core.GetPluginErrorLogger(explorerWorker)); - CriteriaLastModified = new DateTime(0); - } - - public void Clear() - { - SystemBodyHistory.Clear(); - BodySignalHistory.Clear(); - BarycentreHistory.Clear(); - } - - public void RecordSignal(FSSBodySignals bodySignals) - { - if (!BodySignalHistory.ContainsKey(bodySignals.SystemAddress)) - { - BodySignalHistory.Add(bodySignals.SystemAddress, new Dictionary()); - } - - if (!BodySignalHistory[bodySignals.SystemAddress].ContainsKey(bodySignals.BodyID)) - { - BodySignalHistory[bodySignals.SystemAddress].Add(bodySignals.BodyID, bodySignals); - } - } - - public void RecordBarycentre(ScanBaryCentre scan) - { - if (!BarycentreHistory.ContainsKey(scan.SystemAddress)) - { - BarycentreHistory.Add(scan.SystemAddress, new Dictionary()); - } - - if (!BarycentreHistory[scan.SystemAddress].ContainsKey(scan.BodyID)) - { - BarycentreHistory[scan.SystemAddress].Add(scan.BodyID, scan); - } - } - - private static string IncrementOrdinal(string ordinal) - { - var ordChar = ordinal.ToCharArray().Last(); - - if (new[] {'Z', '9'}.Contains(ordChar)) - { - ordinal = IncrementOrdinal(ordinal.Length == 1 ? " " : string.Empty + ordinal[..^1]); - ordChar = (char)(ordChar - 10); - } - - return ordinal[..^1] + (char)(ordChar + 1); - } - - private static string DecrementOrdinal(string ordinal) - { - var ordChar = ordinal.ToCharArray().Last(); - - if (new[] { 'A', '0' }.Contains(ordChar)) - { - ordinal = DecrementOrdinal(ordinal.Length == 1 ? " " : string.Empty + ordinal[..^1]); - ordChar = (char)(ordChar + 10); - } - - return ordinal[..^1] + (char)(ordChar - 1); - } - - public Scan ConvertBarycentre(ScanBaryCentre barycentre, Scan childScan) - { - var childAffix = childScan.BodyName - .Replace(childScan.StarSystem, string.Empty).Trim(); - - string baryName; - - if (!string.IsNullOrEmpty(childAffix)) - { - var childOrdinal = childAffix.ToCharArray().Last(); - - // If the ID is one higher than the barycentre than this is the "first" child body - var lowChild = childScan.BodyID - barycentre.BodyID == 1; - - string baryAffix; - - // Barycentre ordinal always labelled as low before high, e.g. "AB" - if (lowChild) - { - baryAffix = childAffix + "-" + IncrementOrdinal(childAffix); - } - else - { - baryAffix = DecrementOrdinal(childAffix) + "-" + childAffix; - } - - baryName = barycentre.StarSystem + " " + baryAffix; - } - else - { - // Without ordinals it's complicated to determine what the ordinal *should* be. - // Just name the barycentre after the child object. - baryName = childScan.BodyName + " Barycentre"; - } - - Scan barycentreScan = new() - { - Timestamp = barycentre.Timestamp, - BodyName = baryName, - Parents = childScan.Parents.RemoveAt(0), - PlanetClass = "Barycentre", - StarSystem = barycentre.StarSystem, - SystemAddress = barycentre.SystemAddress, - BodyID = barycentre.BodyID, - SemiMajorAxis = barycentre.SemiMajorAxis, - Eccentricity = barycentre.Eccentricity, - OrbitalInclination = barycentre.OrbitalInclination, - Periapsis = barycentre.Periapsis, - OrbitalPeriod = barycentre.OrbitalPeriod, - AscendingNode = barycentre.AscendingNode, - MeanAnomaly = barycentre.MeanAnomaly, - Json = barycentre.Json - }; - - return barycentreScan; - } - public void SetSystem(string potentialNewSystem) - { - if (string.IsNullOrEmpty(currentSystem) || currentSystem != potentialNewSystem) - { - currentSystem = potentialNewSystem; - if (ExplorerWorker.settings.OnlyShowCurrentSystem && !ObservatoryCore.IsLogMonitorBatchReading) - { - ObservatoryCore.SetGridItems(ExplorerWorker, [ - typeof(ExplorerUIResults).GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(p => p.Name) - .ToDictionary(p => p, p => string.Empty) - ]); - Clear(); - } - } - } - - public void ProcessScan(Scan scanEvent, bool readAll) - { - if (!readAll) - { - - // if (File.Exists(criteriaFilePath)) - // { - // var fileModified = new FileInfo(criteriaFilePath).LastWriteTime; - // - // if (fileModified != CriteriaLastModified) - // { - // try - // { - // CustomCriteriaManager.RefreshCriteria(criteriaFilePath); - // } - // catch (CriteriaLoadException e) - // { - // var exceptionResult = new ExplorerUIResults - // { - // BodyName = "Error Reading Custom Criteria File", - // Time = DateTime.Now.ToString("G"), - // Description = e.Message, - // Details = e.OriginalScript - // }; - // ObservatoryCore.AddGridItem(ExplorerWorker, exceptionResult); - // } - // - // CriteriaLastModified = fileModified; - // } - // } - } - - Dictionary systemBodies; - if (SystemBodyHistory.ContainsKey(scanEvent.SystemAddress)) - { - systemBodies = SystemBodyHistory[scanEvent.SystemAddress]; - if (systemBodies.ContainsKey(scanEvent.BodyID)) - { - if (scanEvent.SystemAddress != 0) - { - //We've already checked this object. - return; - } - } - } - else - { - systemBodies = new(); - SystemBodyHistory.Add(scanEvent.SystemAddress, systemBodies); - } - - if (SystemBodyHistory.Count > 1000) - { - foreach (var entry in SystemBodyHistory.Where(entry => entry.Key != scanEvent.SystemAddress).ToList()) - { - SystemBodyHistory.Remove(entry.Key); - } - SystemBodyHistory.TrimExcess(); - } - - if (scanEvent.SystemAddress != 0 && !systemBodies.ContainsKey(scanEvent.BodyID)) - systemBodies.Add(scanEvent.BodyID, scanEvent); - - var results = DefaultCriteria.CheckInterest(scanEvent, SystemBodyHistory, BodySignalHistory, ExplorerWorker.settings); - - if (BarycentreHistory.ContainsKey(scanEvent.SystemAddress) && scanEvent.Parent != null && BarycentreHistory[scanEvent.SystemAddress].ContainsKey(scanEvent.Parent[0].Body)) - { - ProcessScan(ConvertBarycentre(BarycentreHistory[scanEvent.SystemAddress][scanEvent.Parent[0].Body], scanEvent), readAll); - } - - // if (ExplorerWorker.settings.EnableCustomCriteria) - // results.AddRange(CustomCriteriaManager.CheckInterest(scanEvent, SystemBodyHistory, BodySignalHistory, ExplorerWorker.settings)); - - if (results.Count > 0) - { - StringBuilder notificationDetail = new(); - StringBuilder notificationExtendedDetail = new(); - foreach (var result in results) - { - var scanResult = new ExplorerUIResults - { - BodyName = result.SystemWide ? scanEvent.StarSystem : scanEvent.BodyName, - Time = scanEvent.TimestampDateTime.ToString("G"), - Description = result.Description, - Details = result.Detail - }; - ObservatoryCore.AddGridItem(ExplorerWorker, scanResult); - notificationDetail.AppendLine(result.Description); - notificationExtendedDetail.AppendLine(result.Detail); - } - - string bodyAffix; - - if (scanEvent.StarSystem != null && scanEvent.BodyName.StartsWith(scanEvent.StarSystem)) - { - bodyAffix = scanEvent.BodyName.Replace(scanEvent.StarSystem, string.Empty); - } - else - { - bodyAffix = string.Empty; - } - - var bodyLabel = SecurityElement.Escape(scanEvent.PlanetClass == "Barycentre" ? "Barycentre" : "Body"); - - string spokenAffix; - - if (bodyAffix.Length > 0) - { - if (bodyAffix.Contains("Ring")) - { - var ringIndex = bodyAffix.Length - 6; - // spokenAffix = - // "" + bodyAffix[..ringIndex] - // + "" + SplitOrdinalForSsml(bodyAffix.Substring(ringIndex, 1)) - // + bodyAffix[(ringIndex + 1)..]; - } - else - { - //spokenAffix = SplitOrdinalForSsml(bodyAffix); - } - } - else - { - bodyLabel = "Primary Star"; - spokenAffix = string.Empty; - } - - throw new NotImplementedException("Scan Complete Notification Not Implemented"); - // NotificationArgs args = new() - // { - // Title = bodyLabel + bodyAffix, - // TitleSsml = $"{bodyLabel} {spokenAffix}", - // Detail = notificationDetail.ToString(), - // Sender = ExplorerWorker.ShortName, - // ExtendedDetails = notificationExtendedDetail.ToString(), - // CoalescingId = scanEvent.BodyID, - // }; - // - // ObservatoryCore.SendNotification(args); - } - } -} \ No newline at end of file diff --git a/Explorer/Explorer.csproj b/Explorer/Explorer.csproj deleted file mode 100644 index 94401ae..0000000 --- a/Explorer/Explorer.csproj +++ /dev/null @@ -1,17 +0,0 @@ - - - - net8.0 - enable - Explorer - - - - - - - - - - - diff --git a/Explorer/Explorer.sln b/Explorer/Explorer.sln deleted file mode 100644 index 7481c63..0000000 --- a/Explorer/Explorer.sln +++ /dev/null @@ -1,25 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 16 -VisualStudioVersion = 16.0.31205.134 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ObservatoryExplorer", "ObservatoryExplorer.csproj", "{E0FCF2A2-BF56-4F4D-836B-92A0E8269192}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Debug|Any CPU.Build.0 = Debug|Any CPU - {E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Release|Any CPU.ActiveCfg = Release|Any CPU - {E0FCF2A2-BF56-4F4D-836B-92A0E8269192}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {196B0F23-25FC-4A58-A7A9-2676C7749FFD} - EndGlobalSection -EndGlobal diff --git a/Explorer/ExplorerSettings.cs b/Explorer/ExplorerSettings.cs deleted file mode 100644 index 2663be1..0000000 --- a/Explorer/ExplorerSettings.cs +++ /dev/null @@ -1,78 +0,0 @@ -using Observatory.Framework; - -namespace Explorer; - -public class ExplorerSettings -{ - [SettingDisplayName("Only Show Current System")] - public bool OnlyShowCurrentSystem { get; set; } - - [SettingDisplayName("Landable & Terraformable")] - public bool LandableTerraformable { get; set; } - - [SettingDisplayName("Landable w/ Atmosphere")] - public bool LandableAtmosphere { get; set; } - - [SettingDisplayName("Landable High-g")] - public bool LandableHighG { get; set; } - - [SettingDisplayName("Landable Large")] - public bool LandableLarge { get; set; } - - [SettingDisplayName("Close Orbit")] - public bool CloseOrbit { get; set; } - - [SettingDisplayName("Shepherd Moon")] - public bool ShepherdMoon { get; set; } - - [SettingDisplayName("Wide Ring")] - public bool WideRing { get; set; } - - [SettingDisplayName("Close Binary")] - public bool CloseBinary { get; set; } - - [SettingDisplayName("Colliding Binary")] - public bool CollidingBinary { get; set; } - - [SettingDisplayName("Close Ring Proximity")] - public bool CloseRing { get; set; } - - [SettingDisplayName("Codex Discoveries")] - public bool Codex { get; set; } - - [SettingDisplayName("Uncommon Secondary Star")] - public bool UncommonSecondary { get; set; } - - [SettingDisplayName("Landable w/ Ring")] - public bool LandableRing { get; set; } - - [SettingDisplayName("Nested Moon")] - public bool Nested { get; set; } - - [SettingDisplayName("Small Object")] - public bool SmallObject { get; set; } - - [SettingDisplayName("Fast Rotation")] - public bool FastRotation { get; set; } - - [SettingDisplayName("Fast Orbit")] - public bool FastOrbit { get; set; } - - [SettingDisplayName("High Eccentricity")] - public bool HighEccentricity { get; set; } - - [SettingDisplayName("Diverse Life")] - public bool DiverseLife { get; set; } - - [SettingDisplayName("Good FSD Injection")] - public bool GoodFSDBody { get; set; } - - [SettingDisplayName("All FSD Mats In System")] - public bool GreenSystem { get; set; } - - [SettingDisplayName("All Surface Mats In System")] - public bool GoldSystem { get; set; } - - [SettingDisplayName("High-Value Body")] - public bool HighValueMappable { get; set; } -} \ No newline at end of file diff --git a/Explorer/ExplorerUIResults.cs b/Explorer/ExplorerUIResults.cs deleted file mode 100644 index 8e13775..0000000 --- a/Explorer/ExplorerUIResults.cs +++ /dev/null @@ -1,12 +0,0 @@ -namespace Explorer; - -public class ExplorerUIResults -{ - public string Time { get; set; } - - public string BodyName { get; set; } - - public string Description { get; set; } - - public string Details { get; set; } -} \ No newline at end of file diff --git a/Explorer/Worker.cs b/Explorer/Worker.cs deleted file mode 100644 index bc6f1c3..0000000 --- a/Explorer/Worker.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System.Collections.ObjectModel; -using System.Reflection; -using Observatory.Framework; -using Observatory.Framework.Files.Journal; -using Observatory.Framework.Files.Journal.Exploration; -using Observatory.Framework.Files.Journal.FleetCarrier; -using Observatory.Framework.Files.Journal.Travel; - -namespace Explorer; - -public class ExplorerWorker : IObservatoryWorker -{ - public ExplorerWorker() - { - settings = new() - { - CloseBinary = true, - CloseOrbit = true, - CloseRing = true, - CollidingBinary = true, - FastRotation = true, - HighEccentricity = true, - LandableAtmosphere = true, - LandableHighG = true, - LandableLarge = true, - LandableRing = true, - LandableTerraformable = true, - Nested = true, - ShepherdMoon = true, - SmallObject = true, - WideRing = true - }; - resultsGrid = new(); - } - - private Explorer explorer; - private ObservableCollection resultsGrid; - private IObservatoryCore Core; - - private bool recordProcessedSinceBatchStart; - - public string Name => "Observatory Explorer"; - - public string ShortName => "Explorer"; - - public string Version => typeof(ExplorerWorker).Assembly.GetName().Version.ToString(); - - private PluginUI pluginUI; - - public PluginUI PluginUI => pluginUI; - - public void Load(IObservatoryCore observatoryCore) - { - explorer = new Explorer(this, observatoryCore); - resultsGrid.Add(new ExplorerUIResults()); - pluginUI = new PluginUI(resultsGrid); - Core = observatoryCore; - } - - public void JournalEvent(TJournal journal) where TJournal : JournalBase - { - switch (journal) - { - case Scan scan: - explorer.ProcessScan(scan, Core.IsLogMonitorBatchReading && recordProcessedSinceBatchStart); - // Set this *after* the first scan processes so that we get the current custom criteria file. - if (Core.IsLogMonitorBatchReading) recordProcessedSinceBatchStart = true; - break; - case FSSBodySignals signals: - explorer.RecordSignal(signals); - break; - case ScanBaryCentre barycentre: - explorer.RecordBarycentre(barycentre); - break; - case FSDJump fsdjump: - if (fsdjump is CarrierJump && !((CarrierJump)fsdjump).Docked) - break; - explorer.SetSystem(fsdjump.StarSystem); - break; - case Location location: - explorer.SetSystem(location.StarSystem); - break; - case DiscoveryScan discoveryScan: - break; - case FSSDiscoveryScan discoveryScan: - break; - case FSSSignalDiscovered signalDiscovered: - break; - case NavBeaconScan beaconScan: - break; - case SAAScanComplete scanComplete: - break; - case SAASignalsFound signalsFound: - break; - } - - } - - public void LogMonitorStateChanged(LogMonitorStateChangedEventArgs args) - { - if (LogMonitorStateChangedEventArgs.IsBatchRead(args.NewState)) - { - // Beginning a batch read. Clear grid. - recordProcessedSinceBatchStart = false; - Core.SetGridItems(this, [ - typeof(ExplorerUIResults).GetProperties(BindingFlags.Public | BindingFlags.Instance) - .Select(p => p.Name) - .ToDictionary(p => p, p => string.Empty) - ]); - explorer.Clear(); - } - } - - public object Settings - { - get => settings; - set => settings = (ExplorerSettings)value; - } - - internal ExplorerSettings settings; -} \ No newline at end of file diff --git a/ObservatoryFramework/EventArgs.cs b/ObservatoryFramework/EventArgs.cs index 14f69fb..5c5d055 100644 --- a/ObservatoryFramework/EventArgs.cs +++ b/ObservatoryFramework/EventArgs.cs @@ -1,20 +1,22 @@ -namespace Observatory.Framework; +using System.Text.Json.Nodes; + +namespace Observatory.Framework; /// /// Provides data for Elite Dangerous journal events. /// -public class JournalEventArgs : EventArgs +public class JournalEventArgs(string journalEventType, JsonObject journalEvent) : EventArgs { /// /// Type of journal entry that triggered event. - /// Will be a class from the Observatory.Framework.Files.Journal namespace derived from JournalBase, or JournalBase itself in the case of an unhandled journal event type. /// - public Type journalType; + public string JournalEventType = journalEventType; + /// /// Elite Dangerous journal event, deserialized into a .NET object of the type specified by JournalEventArgs.journalType. /// Unhandled json values within a journal entry type will be contained in member property:
Dictionary<string, object> AdditionalProperties.
///
- public object journalEvent; + public JsonObject JournalEvent = journalEvent; } /// @@ -26,48 +28,59 @@ public class NotificationArgs /// Text typically displayed as header content. /// public string Title; + /// /// SSML representation of Title text.
/// This value is optional, if omitted the value of NotificationArgs.Title will be used for voice synthesis without markup. ///
public string TitleSsml; + /// /// Text typically displayed as body content. /// public string Detail; + /// /// SSML representation of Detail text.
/// This value is optional, if omitted the value of NotificationArgs.Detail will be used for voice synthesis without markup. ///
public string DetailSsml; + /// /// Specify window timeout in ms (overrides Core setting). Specify 0 timeout to persist until removed via IObservatoryCore.CancelNotification. Default -1 (use Core setting). /// public int Timeout = -1; + /// /// Specify window X position as a percentage from upper left corner (overrides Core setting). Default -1.0 (use Core setting). /// public double XPos = -1.0; + /// /// Specify window Y position as a percentage from upper left corner (overrides Core setting). Default -1.0 (use Core setting). /// public double YPos = -1.0; + /// /// Specifies the desired renderings of the notification. Defaults to . /// public NotificationRendering Rendering = NotificationRendering.All; + /// /// Specifies if some part of the notification should be suppressed. Not supported by all notifiers. Defaults to . /// public NotificationSuppression Suppression = NotificationSuppression.None; + /// /// The plugin sending this notification. /// public string Sender = ""; + /// /// Additional notification detailed (generally not rendered by voice or popup; potentially used by aggregating/logging plugins). /// public string ExtendedDetails; + /// /// A value which allows grouping of notifications together. For example, values >= 0 <= 1000 could be system body IDs, -1 is the system, anything else is arbitrary. /// @@ -84,10 +97,12 @@ public enum NotificationSuppression /// No suppression. /// None = 0, + /// /// Suppress title. /// Title = 1, + /// /// Suppress detail. /// @@ -104,14 +119,17 @@ public enum NotificationRendering /// Send notification to native visual popup notificaiton handler. /// NativeVisual = 1, + /// /// Send notification to native speech notification handler. /// NativeVocal = 2, + /// /// Send notification to all installed notifier plugins. /// PluginNotifier = 4, + /// /// Send notification to all available handlers. /// @@ -128,18 +146,21 @@ public enum LogMonitorState : uint /// Monitoring is stopped. /// Idle = 0, + /// /// Real-time monitoring is active. /// Realtime = 1, + /// /// Batch read of historical journals is in progress. /// - Batch = 2, + BatchProcessing = 2, + /// - /// Batch read of recent journals is in progress to establish current player state. + /// Initial read of recent journals to establish current player state is in progress . /// - PreRead = 4 + Init = 4 } /// @@ -164,6 +185,6 @@ public class LogMonitorStateChangedEventArgs : EventArgs /// A boolean; True iff the state provided represents a batch-mode read. public static bool IsBatchRead(LogMonitorState state) { - return state.HasFlag(LogMonitorState.Batch) || state.HasFlag(LogMonitorState.PreRead); + return state.HasFlag(LogMonitorState.BatchProcessing) || state.HasFlag(LogMonitorState.Init); } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Journal/FleetCarrier/CarrierDecommission.cs b/ObservatoryFramework/Files/Journal/FleetCarrier/CarrierDecommission.cs index f20b7cd..c78d1b1 100644 --- a/ObservatoryFramework/Files/Journal/FleetCarrier/CarrierDecommission.cs +++ b/ObservatoryFramework/Files/Journal/FleetCarrier/CarrierDecommission.cs @@ -5,11 +5,5 @@ public class CarrierDecommission : JournalBase public ulong CarrierID { get; init; } public long ScrapRefund { get; init; } public long ScrapTime { get; init; } - public DateTime ScrapTimeUTC - { - get - { - return DateTimeOffset.FromUnixTimeSeconds(ScrapTime).UtcDateTime; - } - } + public DateTime ScrapTimeUTC => DateTimeOffset.FromUnixTimeSeconds(ScrapTime).UtcDateTime; } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Journal/FleetCarrier/CarrierJumpRequest.cs b/ObservatoryFramework/Files/Journal/FleetCarrier/CarrierJumpRequest.cs index e46030d..c107492 100644 --- a/ObservatoryFramework/Files/Journal/FleetCarrier/CarrierJumpRequest.cs +++ b/ObservatoryFramework/Files/Journal/FleetCarrier/CarrierJumpRequest.cs @@ -1,6 +1,4 @@ -using System.Text.Json.Serialization; - -namespace Observatory.Framework.Files.Journal.FleetCarrier; +namespace Observatory.Framework.Files.Journal.FleetCarrier; public class CarrierJumpRequest : JournalBase { @@ -10,10 +8,5 @@ public class CarrierJumpRequest : JournalBase public ulong CarrierID { get; init; } public string SystemName { get; init; } public ulong SystemID { get; init; } - public string DepartureTime { get; init; } - - [JsonIgnore] - public DateTimeOffset DepartureTimeDateTime { - get => ParseDateTime(DepartureTime); - } + public DateTimeOffset DepartureTime { get; init; } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Journal/JournalBase.cs b/ObservatoryFramework/Files/Journal/JournalBase.cs index 861d87b..ba975c0 100644 --- a/ObservatoryFramework/Files/Journal/JournalBase.cs +++ b/ObservatoryFramework/Files/Journal/JournalBase.cs @@ -1,52 +1,289 @@ -using System.Globalization; -using System.Text.Json.Serialization; +using System.Text.Json.Serialization; +using Observatory.Framework.Files.Journal.Combat; +using Observatory.Framework.Files.Journal.Exploration; +using Observatory.Framework.Files.Journal.FleetCarrier; +using Observatory.Framework.Files.Journal.Odyssey; +using Observatory.Framework.Files.Journal.Other; +using Observatory.Framework.Files.Journal.Powerplay; +using Observatory.Framework.Files.Journal.Squadron; +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 Observatory.Framework.Files.Journal; - +[JsonDerivedType(typeof(BackpackFile))] +[JsonDerivedType(typeof(CargoFile))] +[JsonDerivedType(typeof(FCMaterialsFile))] +[JsonDerivedType(typeof(Bounty))] +[JsonDerivedType(typeof(CapShipBound))] +[JsonDerivedType(typeof(Died))] +[JsonDerivedType(typeof(EscapeInterdiction))] +[JsonDerivedType(typeof(FactionKillBond))] +[JsonDerivedType(typeof(FighterDestroyed))] +[JsonDerivedType(typeof(HeatDamage))] +[JsonDerivedType(typeof(HeatWarning))] +[JsonDerivedType(typeof(HullDamage))] +[JsonDerivedType(typeof(Interdicted))] +[JsonDerivedType(typeof(Interdiction))] +[JsonDerivedType(typeof(PVPKill))] +[JsonDerivedType(typeof(SRVDestroyed))] +[JsonDerivedType(typeof(ShieldState))] +[JsonDerivedType(typeof(ShipTargeted))] +[JsonDerivedType(typeof(UnderAttack))] +[JsonDerivedType(typeof(BuyExplorationData))] +[JsonDerivedType(typeof(CodexEntry))] +[JsonDerivedType(typeof(DiscoveryScan))] +[JsonDerivedType(typeof(FSSAllBodiesFound))] +[JsonDerivedType(typeof(FSSBodySignals))] +[JsonDerivedType(typeof(FSSDiscoveryScan))] +[JsonDerivedType(typeof(FSSSignalDiscovered))] +[JsonDerivedType(typeof(MaterialCollected))] +[JsonDerivedType(typeof(MaterialDiscarded))] +[JsonDerivedType(typeof(MaterialDiscovered))] +[JsonDerivedType(typeof(MultiSellExplorationData))] +[JsonDerivedType(typeof(NavBeaconScan))] +[JsonDerivedType(typeof(SAAScanComplete))] +[JsonDerivedType(typeof(SAASignalsFound))] +[JsonDerivedType(typeof(Scan))] +[JsonDerivedType(typeof(ScanBaryCentre))] +[JsonDerivedType(typeof(Screenshot))] +[JsonDerivedType(typeof(SellExplorationData))] +[JsonDerivedType(typeof(CarrierBankTransfer))] +[JsonDerivedType(typeof(CarrierBuy))] +[JsonDerivedType(typeof(CarrierCancelDecommission))] +[JsonDerivedType(typeof(CarrierCrewServices))] +[JsonDerivedType(typeof(CarrierDecommission))] +[JsonDerivedType(typeof(CarrierDepositFuel))] +[JsonDerivedType(typeof(CarrierDockingPermission))] +[JsonDerivedType(typeof(CarrierFinance))] +[JsonDerivedType(typeof(CarrierJump))] +[JsonDerivedType(typeof(CarrierJumpCancelled))] +[JsonDerivedType(typeof(CarrierJumpRequest))] +[JsonDerivedType(typeof(CarrierModulePack))] +[JsonDerivedType(typeof(CarrierShipPack))] +[JsonDerivedType(typeof(CarrierStats))] +[JsonDerivedType(typeof(CarrierTradeOrder))] +[JsonDerivedType(typeof(FCMaterlas))] +[JsonDerivedType(typeof(InvalidJson))] +[JsonDerivedType(typeof(BackPack))] +[JsonDerivedType(typeof(BackpackChange))] +[JsonDerivedType(typeof(BackpackMaterials))] +[JsonDerivedType(typeof(BookDropship))] +[JsonDerivedType(typeof(BookTaxi))] +[JsonDerivedType(typeof(BuyMicroResources))] +[JsonDerivedType(typeof(BuySuit))] +[JsonDerivedType(typeof(BuyWeapon))] +[JsonDerivedType(typeof(CancelDropship))] +[JsonDerivedType(typeof(CancelTaxi))] +[JsonDerivedType(typeof(CollectItems))] +[JsonDerivedType(typeof(CreateSuitLoadout))] +[JsonDerivedType(typeof(DeleteSuitLoadout))] +[JsonDerivedType(typeof(Disembark))] +[JsonDerivedType(typeof(DropItems))] +[JsonDerivedType(typeof(DropShipDeploy))] +[JsonDerivedType(typeof(Embark))] +[JsonDerivedType(typeof(FCMaterials))] +[JsonDerivedType(typeof(LoadoutEquipModule))] +[JsonDerivedType(typeof(LoadoutRemoveModule))] +[JsonDerivedType(typeof(RenameSuitLoadout))] +[JsonDerivedType(typeof(ScanOrganic))] +[JsonDerivedType(typeof(SellMicroResources))] +[JsonDerivedType(typeof(SellOrganicData))] +[JsonDerivedType(typeof(SellSuit))] +[JsonDerivedType(typeof(SellWeapon))] +[JsonDerivedType(typeof(ShipLockerMaterials))] +[JsonDerivedType(typeof(SuitLoadout))] +[JsonDerivedType(typeof(SwitchSuitLoadout))] +[JsonDerivedType(typeof(TradeMicroResources))] +[JsonDerivedType(typeof(TransferMicroResources))] +[JsonDerivedType(typeof(UpgradeSuit))] +[JsonDerivedType(typeof(UpgradeWeapon))] +[JsonDerivedType(typeof(UseConsumable))] +[JsonDerivedType(typeof(AfmuRepairs))] +[JsonDerivedType(typeof(ApproachSettlement))] +[JsonDerivedType(typeof(CargoTransfer))] +[JsonDerivedType(typeof(ChangeCrewRole))] +[JsonDerivedType(typeof(CockpitBreached))] +[JsonDerivedType(typeof(CommitCrime))] +[JsonDerivedType(typeof(Continued))] +[JsonDerivedType(typeof(CrewLaunchFighter))] +[JsonDerivedType(typeof(CrewMemberJoins))] +[JsonDerivedType(typeof(CrewMemberQuits))] +[JsonDerivedType(typeof(CrewMemberRoleChange))] +[JsonDerivedType(typeof(CrimeVictim))] +[JsonDerivedType(typeof(DataScanned))] +[JsonDerivedType(typeof(DatalinkScan))] +[JsonDerivedType(typeof(DatalinkVoucher))] +[JsonDerivedType(typeof(DockFighter))] +[JsonDerivedType(typeof(DockSRV))] +[JsonDerivedType(typeof(EndCrewSession))] +[JsonDerivedType(typeof(FighterRebuilt))] +[JsonDerivedType(typeof(Friends))] +[JsonDerivedType(typeof(FuelScoop))] +[JsonDerivedType(typeof(JetConeBoost))] +[JsonDerivedType(typeof(JetConeDamage))] +[JsonDerivedType(typeof(JoinACrew))] +[JsonDerivedType(typeof(KickCrewMember))] +[JsonDerivedType(typeof(LaunchDrone))] +[JsonDerivedType(typeof(LaunchFighter))] +[JsonDerivedType(typeof(LaunchSRV))] +[JsonDerivedType(typeof(ModuleInfo))] +[JsonDerivedType(typeof(Music))] +[JsonDerivedType(typeof(NpcCrewPaidWage))] +[JsonDerivedType(typeof(NpcCrewRank))] +[JsonDerivedType(typeof(Promotion))] +[JsonDerivedType(typeof(ProspectedAsteroid))] +[JsonDerivedType(typeof(QuitACrew))] +[JsonDerivedType(typeof(RebootRepair))] +[JsonDerivedType(typeof(ReceiveText))] +[JsonDerivedType(typeof(RepairDrone))] +[JsonDerivedType(typeof(ReservoirReplenished))] +[JsonDerivedType(typeof(Resurrect))] +[JsonDerivedType(typeof(Scanned))] +[JsonDerivedType(typeof(SelfDestruct))] +[JsonDerivedType(typeof(SendText))] +[JsonDerivedType(typeof(Shutdown))] +[JsonDerivedType(typeof(Synthesis))] +[JsonDerivedType(typeof(SystemsShutdown))] +[JsonDerivedType(typeof(USSDrop))] +[JsonDerivedType(typeof(VehicleSwitch))] +[JsonDerivedType(typeof(WingAdd))] +[JsonDerivedType(typeof(WingInvite))] +[JsonDerivedType(typeof(WingJoin))] +[JsonDerivedType(typeof(WingLeave))] +[JsonDerivedType(typeof(PowerplayCollect))] +[JsonDerivedType(typeof(PowerplayDefect))] +[JsonDerivedType(typeof(PowerplayDeliver))] +[JsonDerivedType(typeof(PowerplayFastTrack))] +[JsonDerivedType(typeof(PowerplayJoin))] +[JsonDerivedType(typeof(PowerplayLeave))] +[JsonDerivedType(typeof(PowerplaySalary))] +[JsonDerivedType(typeof(PowerplayVote))] +[JsonDerivedType(typeof(PowerplayVoucher))] +[JsonDerivedType(typeof(AppliedToSquadron))] +[JsonDerivedType(typeof(DisbandedSquadron))] +[JsonDerivedType(typeof(InvitedToSquadron))] +[JsonDerivedType(typeof(JoinedSquadron))] +[JsonDerivedType(typeof(KickedFromSquadron))] +[JsonDerivedType(typeof(LeftSquadron))] +[JsonDerivedType(typeof(SharedBookmarkToSquadron))] +[JsonDerivedType(typeof(SquadronCreated))] +[JsonDerivedType(typeof(SquadronDemotion))] +[JsonDerivedType(typeof(SquadronPromotion))] +[JsonDerivedType(typeof(SquadronStartup))] +[JsonDerivedType(typeof(WonATrophyForSquadron))] +[JsonDerivedType(typeof(Cargo))] +[JsonDerivedType(typeof(ClearSavedGame))] +[JsonDerivedType(typeof(Commander))] +[JsonDerivedType(typeof(FileHeader))] +[JsonDerivedType(typeof(LoadGame))] +[JsonDerivedType(typeof(Loadout))] +[JsonDerivedType(typeof(Materials))] +[JsonDerivedType(typeof(Missions))] +[JsonDerivedType(typeof(NewCommander))] +[JsonDerivedType(typeof(Passengers))] +[JsonDerivedType(typeof(Startup.Powerplay))] +[JsonDerivedType(typeof(Progress))] +[JsonDerivedType(typeof(Rank))] +[JsonDerivedType(typeof(Reputation))] +[JsonDerivedType(typeof(Statistics))] +[JsonDerivedType(typeof(BuyAmmo))] +[JsonDerivedType(typeof(BuyDrones))] +[JsonDerivedType(typeof(CargoDepot))] +[JsonDerivedType(typeof(ClearImpound))] +[JsonDerivedType(typeof(CommunityGoal))] +[JsonDerivedType(typeof(CommunityGoalDiscard))] +[JsonDerivedType(typeof(CommunityGoalJoin))] +[JsonDerivedType(typeof(CommunityGoalReward))] +[JsonDerivedType(typeof(CrewAssign))] +[JsonDerivedType(typeof(CrewFire))] +[JsonDerivedType(typeof(CrewHire))] +[JsonDerivedType(typeof(EngineerApply))] +[JsonDerivedType(typeof(EngineerContribution))] +[JsonDerivedType(typeof(EngineerCraft))] +[JsonDerivedType(typeof(EngineerLegacyConvert))] +[JsonDerivedType(typeof(EngineerProgress))] +[JsonDerivedType(typeof(FetchRemoteModule))] +[JsonDerivedType(typeof(Market))] +[JsonDerivedType(typeof(MassModuleStore))] +[JsonDerivedType(typeof(MaterialTrade))] +[JsonDerivedType(typeof(MissionAbandoned))] +[JsonDerivedType(typeof(MissionAccepted))] +[JsonDerivedType(typeof(MissionCompleted))] +[JsonDerivedType(typeof(MissionFailed))] +[JsonDerivedType(typeof(MissionRedirected))] +[JsonDerivedType(typeof(ModuleBuy))] +[JsonDerivedType(typeof(ModuleRetrieve))] +[JsonDerivedType(typeof(ModuleSell))] +[JsonDerivedType(typeof(ModuleSellRemote))] +[JsonDerivedType(typeof(ModuleStore))] +[JsonDerivedType(typeof(ModuleSwap))] +[JsonDerivedType(typeof(Outfitting))] +[JsonDerivedType(typeof(PayBounties))] +[JsonDerivedType(typeof(PayFines))] +[JsonDerivedType(typeof(PayLegacyFines))] +[JsonDerivedType(typeof(RedeemVoucher))] +[JsonDerivedType(typeof(RefuelAll))] +[JsonDerivedType(typeof(RefuelPartial))] +[JsonDerivedType(typeof(Repair))] +[JsonDerivedType(typeof(RepairAll))] +[JsonDerivedType(typeof(RestockVehicle))] +[JsonDerivedType(typeof(ScientificResearch))] +[JsonDerivedType(typeof(SearchAndRescue))] +[JsonDerivedType(typeof(SellDrones))] +[JsonDerivedType(typeof(SellShipOnRebuy))] +[JsonDerivedType(typeof(SetUserShipName))] +[JsonDerivedType(typeof(Shipyard))] +[JsonDerivedType(typeof(ShipyardBuy))] +[JsonDerivedType(typeof(ShipyardNew))] +[JsonDerivedType(typeof(ShipyardSell))] +[JsonDerivedType(typeof(ShipyardSwap))] +[JsonDerivedType(typeof(ShipyardTransfer))] +[JsonDerivedType(typeof(StoredModules))] +[JsonDerivedType(typeof(StoredShips))] +[JsonDerivedType(typeof(TechnologyBroker))] +[JsonDerivedType(typeof(AsteroidCracked))] +[JsonDerivedType(typeof(BuyTradeData))] +[JsonDerivedType(typeof(CollectCargo))] +[JsonDerivedType(typeof(EjectCargo))] +[JsonDerivedType(typeof(MarketBuy))] +[JsonDerivedType(typeof(MarketSell))] +[JsonDerivedType(typeof(MiningRefined))] +[JsonDerivedType(typeof(ApproachBody))] +[JsonDerivedType(typeof(Docked))] +[JsonDerivedType(typeof(DockingCancelled))] +[JsonDerivedType(typeof(DockingDenied))] +[JsonDerivedType(typeof(DockingGranted))] +[JsonDerivedType(typeof(DockingRequested))] +[JsonDerivedType(typeof(DockingTimeout))] +[JsonDerivedType(typeof(FSDJump))] +[JsonDerivedType(typeof(FSDTarget))] +[JsonDerivedType(typeof(LeaveBody))] +[JsonDerivedType(typeof(Liftoff))] +[JsonDerivedType(typeof(Location))] +[JsonDerivedType(typeof(NavRoute))] +[JsonDerivedType(typeof(NavRouteClear))] +[JsonDerivedType(typeof(StartJump))] +[JsonDerivedType(typeof(SupercruiseDestinationDrop))] +[JsonDerivedType(typeof(SupercruiseEntry))] +[JsonDerivedType(typeof(SupercruiseExit))] +[JsonDerivedType(typeof(Touchdown))] +[JsonDerivedType(typeof(Undocked))] +[JsonDerivedType(typeof(MarketFile))] +[JsonDerivedType(typeof(ModuleInfoFile))] +[JsonDerivedType(typeof(NavRouteFile))] +[JsonDerivedType(typeof(OutfittingFile))] +[JsonDerivedType(typeof(ShipyardFile))] +[JsonDerivedType(typeof(Status))] public class JournalBase { [JsonPropertyName("timestamp")] - public string Timestamp { get; init; } - - [JsonIgnore] - public DateTimeOffset TimestampDateTime - { - get => ParseDateTime(Timestamp); - } + public DateTimeOffset Timestamp { get; init; } [JsonPropertyName("event")] public string Event { get; init; } [JsonExtensionData] public Dictionary AdditionalProperties { get; init; } - - [JsonIgnore] - public string Json - { - get => json; - set - { - if (json == null || string.IsNullOrWhiteSpace(json)) - { - json = value; - } - else - { - throw new Exception("Journal property \"Json\" can only be set while empty."); - } - } - } - - private string json; - - // For use by Journal object classes for .*DateTime properties, like TimestampeDateTime, above. - internal static DateTimeOffset ParseDateTime(string value) - { - if (DateTime.TryParseExact(value, "yyyy-MM-ddTHH:mm:ssZ", null, DateTimeStyles.AssumeUniversal, out var dateTimeValue)) - { - return dateTimeValue; - } - - return new DateTime(); - } } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Journal/JournalUtilities.cs b/ObservatoryFramework/Files/Journal/JournalUtilities.cs index 018a3dd..32a31da 100644 --- a/ObservatoryFramework/Files/Journal/JournalUtilities.cs +++ b/ObservatoryFramework/Files/Journal/JournalUtilities.cs @@ -1,34 +1,12 @@ -using System.Text; -using System.Text.Json; +using System.Text.Json.Nodes; namespace Observatory.Framework.Files.Journal; public static class JournalUtilities { - public static string GetEventType(string line) + public static string? GetEventType(JsonObject? line) { - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(line)); - var result = string.Empty; - - try - { - while (reader.Read()) - { - if (reader.TokenType == JsonTokenType.PropertyName && reader.GetString() == "event") - { - reader.Read(); - result = reader.GetString(); - } - } - } - catch - { - - result = "InvalidJson"; - } - - - return result; + return line.ContainsKey("event") ? line["event"]?.ToString() : null; } public static string CleanScanEvent(string line) @@ -38,6 +16,6 @@ public static class JournalUtilities public const string ObsoleteMessage = "Unused in Elite Dangerous 3.7+, may appear in legacy journal data."; - public const string UnusedMessage = "Documented by Frontier, but no occurances of this value ever found in real journal data."; - + public const string UnusedMessage = + "Documented by Frontier, but no occurances of this value ever found in real journal data."; } \ No newline at end of file diff --git a/ObservatoryFramework/Files/Journal/Travel/Docked.cs b/ObservatoryFramework/Files/Journal/Travel/Docked.cs index 88234fc..2a93f85 100644 --- a/ObservatoryFramework/Files/Journal/Travel/Docked.cs +++ b/ObservatoryFramework/Files/Journal/Travel/Docked.cs @@ -22,23 +22,14 @@ public class Docked : JournalBase [Obsolete(JournalUtilities.ObsoleteMessage), JsonConverter(typeof(LegacyFactionConverter))] public Faction Faction { - private get - { - return StationFaction; - } - init - { - StationFaction = value; - } + private get => StationFaction; + init => StationFaction = value; } [Obsolete(JournalUtilities.ObsoleteMessage)] public string FactionState { - private get - { - return StationFaction.FactionState; - } + private get => StationFaction.FactionState; init { @@ -51,24 +42,24 @@ public class Docked : JournalBase [Obsolete(JournalUtilities.ObsoleteMessage)] public string Government { - private get { return StationGovernment; } - init { StationGovernment = value; } + private get => StationGovernment; + init => StationGovernment = value; } public string StationGovernment_Localised { get; init; } [Obsolete(JournalUtilities.ObsoleteMessage)] public string Government_Localised { - private get { return StationGovernment_Localised; } - init { StationGovernment_Localised = value; } + private get => StationGovernment_Localised; + init => StationGovernment_Localised = value; } public string StationAllegiance { get; init; } [Obsolete(JournalUtilities.ObsoleteMessage)] public string Allegiance { - private get { return StationAllegiance; } - init { StationAllegiance = value; } + private get => StationAllegiance; + init => StationAllegiance = value; } [JsonConverter(typeof(StationServiceConverter))] @@ -78,16 +69,16 @@ public class Docked : JournalBase [Obsolete(JournalUtilities.ObsoleteMessage)] public string Economy { - private get { return StationEconomy; } - init { StationEconomy = value; } + private get => StationEconomy; + init => StationEconomy = value; } public string StationEconomy_Localised { get; init; } [Obsolete(JournalUtilities.ObsoleteMessage)] public string Economy_Localised { - private get { return StationEconomy_Localised; } - init { StationEconomy_Localised = value; } + private get => StationEconomy_Localised; + init => StationEconomy_Localised = value; } public ImmutableList StationEconomies { get; init; } diff --git a/ObservatoryFramework/Files/Journal/Travel/FSDJump.cs b/ObservatoryFramework/Files/Journal/Travel/FSDJump.cs index 8d198ad..a67bda2 100644 --- a/ObservatoryFramework/Files/Journal/Travel/FSDJump.cs +++ b/ObservatoryFramework/Files/Journal/Travel/FSDJump.cs @@ -23,10 +23,7 @@ public class FSDJump : JournalBase [Obsolete(JournalUtilities.ObsoleteMessage)] public string FactionState { - get - { - return SystemFaction.FactionState; - } + get => SystemFaction.FactionState; init { //Stale Data, discard diff --git a/ObservatoryFramework/Files/Journal/Travel/Location.cs b/ObservatoryFramework/Files/Journal/Travel/Location.cs index 0533890..b63a20b 100644 --- a/ObservatoryFramework/Files/Journal/Travel/Location.cs +++ b/ObservatoryFramework/Files/Journal/Travel/Location.cs @@ -14,10 +14,7 @@ public class Location : JournalBase [Obsolete(JournalUtilities.ObsoleteMessage)] public string FactionState { - get - { - return SystemFaction.FactionState; - } + get => SystemFaction.FactionState; init { //Stale Data, discard diff --git a/ObservatoryFramework/Files/ParameterTypes/CurrentGoal.cs b/ObservatoryFramework/Files/ParameterTypes/CurrentGoal.cs index e898e4c..b3431b5 100644 --- a/ObservatoryFramework/Files/ParameterTypes/CurrentGoal.cs +++ b/ObservatoryFramework/Files/ParameterTypes/CurrentGoal.cs @@ -1,6 +1,4 @@ -using Observatory.Framework.Files.Journal; - -namespace Observatory.Framework.Files.ParameterTypes; +namespace Observatory.Framework.Files.ParameterTypes; public class CurrentGoal { @@ -8,11 +6,7 @@ public class CurrentGoal public string Title { get; init; } public string SystemName { get; init; } public string MarketName { get; init; } - public string Expiry { get; init; } - public DateTimeOffset ExpiryDateTime - { - get => JournalBase.ParseDateTime(Expiry); - } + public DateTimeOffset Expiry { get; init; } public bool IsComplete { get; init; } public long CurrentTotal { get; init; } public long PlayerContribution { get; init; } diff --git a/ObservatoryFramework/Files/ParameterTypes/MaterialTrader.cs b/ObservatoryFramework/Files/ParameterTypes/MaterialTrader.cs index 3f6eb5a..d63d1df 100644 --- a/ObservatoryFramework/Files/ParameterTypes/MaterialTrader.cs +++ b/ObservatoryFramework/Files/ParameterTypes/MaterialTrader.cs @@ -22,13 +22,7 @@ public class MaterialTrader [JsonPropertyName("Raw_Materials_Traded")] public int RawMaterialsTraded { get; init; } - public int DataMaterialsTraded - { - get - { - return MaterialsTraded - EncodedMaterialsTraded - RawMaterialsTraded; - } - } + public int DataMaterialsTraded => MaterialsTraded - EncodedMaterialsTraded - RawMaterialsTraded; [JsonPropertyName("Grade_1_Materials_Traded")] public int Grade1MaterialsTraded { get; init; } diff --git a/ObservatoryFramework/IObservatoryCore.cs b/ObservatoryFramework/IObservatoryCore.cs new file mode 100644 index 0000000..b68db45 --- /dev/null +++ b/ObservatoryFramework/IObservatoryCore.cs @@ -0,0 +1,81 @@ +using Observatory.Framework.Files; + +namespace Observatory.Framework; + +/// +/// Interface passed by Observatory Core to plugins. Primarily used for sending notifications and UI updates back to Core. +/// +public interface IObservatoryCore +{ + /// + /// Send a notification out to all native notifiers and any plugins implementing IObservatoryNotifier. + /// + /// Title text for notification. + /// Detail/body text for notificaiton. + /// Guid associated with the notification during its lifetime. Used as an argument with CancelNotification and UpdateNotification. + public Guid SendNotification(string title, string detail); + + /// + /// Send a notification with arguments out to all native notifiers and any plugins implementing IObservatoryNotifier. + /// + /// NotificationArgs object specifying notification content and behaviour. + /// Guid associated with the notification during its lifetime. Used as an argument with CancelNotification and UpdateNotification. + public Guid SendNotification(NotificationArgs notificationEventArgs); + + /// + /// Cancel or close an active notification. + /// + /// Guid of notification to be cancelled. + public void CancelNotification(Guid notificationId); + + /// + /// Update an active notification with a new set of NotificationsArgs. Timeout values are reset and begin counting again from zero if specified. + /// + /// Guid of notification to be updated. + /// NotificationArgs object specifying updated notification content and behaviour. + public void UpdateNotification(Guid notificationId, NotificationArgs notificationEventArgs); + + /// + /// Requests current Elite Dangerous status.json content. + /// + /// Status object reflecting current Elite Dangerous player status. + public Status GetStatus(); + + /// + /// Version string of Observatory Core. + /// + public string Version { get; } + + /// + /// Returns a delegate for logging an error for the calling plugin. A plugin can wrap this method + /// or pass it along to its collaborators. + /// + /// The calling plugin + public Action GetPluginErrorLogger(IObservatoryPlugin plugin); + + /// + /// Shared application HttpClient object. Provided so that plugins can adhere to .NET recommended behaviour of a single HttpClient object per application. + /// + public HttpClient HttpClient { get; } + + /// + /// Returns the current LogMonitor state. + /// + public LogMonitorState CurrentLogMonitorState { get; } + + /// + /// Returns true if the current LogMonitor state represents a batch-read mode. + /// + public bool IsLogMonitorBatchReading { get; } + + /// + /// Retrieves and ensures creation of a location which can be used by the plugin to store persistent data. + /// + public string PluginStorageFolder { get; } + + /// + /// Sends arbitrary data to all other plugins. The full name and version of the sending plugin will be used to identify the sender to any recipients. + /// + /// Utf8 data to be sent. Must be serializable to JSON. + public void SendPluginMessage(IObservatoryPlugin plugin, ReadOnlySpan message); +} \ No newline at end of file diff --git a/ObservatoryFramework/Interfaces.cs b/ObservatoryFramework/Interfaces.cs index 6e8fb1a..9f4c11d 100644 --- a/ObservatoryFramework/Interfaces.cs +++ b/ObservatoryFramework/Interfaces.cs @@ -1,5 +1,4 @@ -using System.Collections; -using Observatory.Framework.Files; +using Observatory.Framework.Files; using Observatory.Framework.Files.Journal; namespace Observatory.Framework; @@ -26,7 +25,7 @@ public interface IObservatoryPlugin /// Short name of the plugin. Used as the tab title for the plugin UI.
/// Can be omitted, in which case the full Name will be used. ///
- public string ShortName { get => Name; } + public string ShortName => Name; /// /// Version string displayed in the Core settings tab's plugin list.
@@ -39,23 +38,6 @@ public interface IObservatoryPlugin ///
public PluginUI PluginUI { get; } - /// - /// Accessors for plugin settings object. Should be initialized with a default state during the plugin constructor. - /// Saving and loading of settings is handled by Observatory Core, and any previously saved settings will be set after plugin instantiation, but before Load() is called. - /// A plugin's settings class is expected to consist of properties with public getters and setters. The settings UI will be automatically generated based on each property type.
- /// The [SettingDisplayName(string name)] attribute can be used to specify a display name, otherwise the name of the property will be used.
- /// Private or internal properties and methods are ignored and can be used for backing values or any other purpose.
- /// If a public property is necessary but not required to be user accessible the [SettingIgnore] property will suppress display.
- ///
- public object Settings { get; set; } - - /// - /// Plugin-specific object implementing the IComparer interface which is used to sort columns in the basic UI datagrid. - /// If omitted a natural sort order is used. - /// - public IObservatoryComparer ColumnSorter - { get => null; } - /// /// Receives data sent by other plugins. /// @@ -93,24 +75,6 @@ public interface IObservatoryWorker : IObservatoryPlugin /// public void LogMonitorStateChanged(LogMonitorStateChangedEventArgs eventArgs) { } - - /// - /// Method called when the user begins "Read All" journal processing, before any journal events are sent.
- /// Used to track if a "Read All" operation is in progress or not to avoid unnecessary processing or notifications.
- /// Can be omitted for plugins which do not require the distinction. - ///
- [Obsolete("Deprecated in favour of LogMonitorStateChanged")] - public void ReadAllStarted() - { } - - /// - /// Method called when "Read All" journal processing completes.
- /// Used to track if a "Read All" operation is in progress or not to avoid unnecessary processing or notifications.
- /// Can be omitted for plugins which do not require the distinction. - ///
- [Obsolete("Deprecated in favour of LogMonitorStateChanged")] - public void ReadAllFinished() - { } } /// @@ -127,116 +91,3 @@ public interface IObservatoryNotifier : IObservatoryPlugin public void OnNotificationEvent(NotificationArgs notificationEventArgs); } -/// -/// Interface passed by Observatory Core to plugins. Primarily used for sending notifications and UI updates back to Core. -/// -public interface IObservatoryCore -{ - /// - /// Send a notification out to all native notifiers and any plugins implementing IObservatoryNotifier. - /// - /// Title text for notification. - /// Detail/body text for notificaiton. - /// Guid associated with the notification during its lifetime. Used as an argument with CancelNotification and UpdateNotification. - public Guid SendNotification(string title, string detail); - - /// - /// Send a notification with arguments out to all native notifiers and any plugins implementing IObservatoryNotifier. - /// - /// NotificationArgs object specifying notification content and behaviour. - /// Guid associated with the notification during its lifetime. Used as an argument with CancelNotification and UpdateNotification. - public Guid SendNotification(NotificationArgs notificationEventArgs); - - /// - /// Cancel or close an active notification. - /// - /// Guid of notification to be cancelled. - public void CancelNotification(Guid notificationId); - - /// - /// Update an active notification with a new set of NotificationsArgs. Timeout values are reset and begin counting again from zero if specified. - /// - /// Guid of notification to be updated. - /// NotificationArgs object specifying updated notification content and behaviour. - public void UpdateNotification(Guid notificationId, NotificationArgs notificationEventArgs); - - /// - /// Add an item to the bottom of the basic UI grid. - /// - /// Reference to the calling plugin's worker interface. - /// Grid item to be added. Object type should match original template item used to create the grid. - public void AddGridItem(IObservatoryWorker worker, object item); - - /// - /// Add multiple items to the bottom of the basic UI grid. - /// - /// Reference to the calling plugin's worker interface. - /// Grid items to be added. Object types should match original template item used to create the grid. - public void AddGridItems(IObservatoryWorker worker, IEnumerable> items); - - /// - /// Replace the contents of the grid with the provided items. - /// - /// Reference to the calling plugin's worker interface. - /// Grid items to be added. Object types should match original template item used to create the grid. - public void SetGridItems(IObservatoryWorker worker, IEnumerable> items); - - /// - /// Requests current Elite Dangerous status.json content. - /// - /// Status object reflecting current Elite Dangerous player status. - public Status GetStatus(); - - /// - /// Version string of Observatory Core. - /// - public string Version { get; } - - /// - /// Returns a delegate for logging an error for the calling plugin. A plugin can wrap this method - /// or pass it along to its collaborators. - /// - /// The calling plugin - public Action GetPluginErrorLogger(IObservatoryPlugin plugin); - - /// - /// Shared application HttpClient object. Provided so that plugins can adhere to .NET recommended behaviour of a single HttpClient object per application. - /// - public HttpClient HttpClient { get; } - - /// - /// Returns the current LogMonitor state. - /// - public LogMonitorState CurrentLogMonitorState { get; } - - /// - /// Returns true if the current LogMonitor state represents a batch-read mode. - /// - public bool IsLogMonitorBatchReading { get; } - - /// - /// Retrieves and ensures creation of a location which can be used by the plugin to store persistent data. - /// - public string PluginStorageFolder { get; } - - /// - /// Sends arbitrary data to all other plugins. The full name and version of the sending plugin will be used to identify the sender to any recipients. - /// - public void SendPluginMessage(IObservatoryPlugin plugin, object message); -} - -/// -/// Extends the base IComparer interface with exposed values for the column ID and sort order to use. -/// -public interface IObservatoryComparer : IComparer -{ - /// - /// Column ID to be currently sorted by. - /// - public int SortColumn { get; set; } - - /// - /// Current order of sorting. Ascending = 1, Descending = -1, No sorting = 0. - /// - public int Order { get; set; } -} \ No newline at end of file diff --git a/ObservatoryFramework/ObservatoryFramework.csproj b/ObservatoryFramework/ObservatoryFramework.csproj index 4c97cc8..26178c4 100644 --- a/ObservatoryFramework/ObservatoryFramework.csproj +++ b/ObservatoryFramework/ObservatoryFramework.csproj @@ -5,6 +5,7 @@ enable Observatory.Framework Debug;Release;Portable + enable diff --git a/Pulsar.sln b/Pulsar.sln index e7bb23f..6be4d9f 100644 --- a/Pulsar.sln +++ b/Pulsar.sln @@ -5,8 +5,6 @@ VisualStudioVersion = 17.3.32922.545 MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Pulsar", "Pulsar\Pulsar.csproj", "{F389FFD8-B189-4A9F-A077-8D355BA23328}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Explorer", "Explorer\Explorer.csproj", "{6CDB4320-1DC9-405A-97E8-272C9B4CD27C}" -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Botanist", "Botanist\Botanist.csproj", "{25F01564-0E35-471B-A9AC-C61C83AF3275}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ObservatoryFramework", "ObservatoryFramework\ObservatoryFramework.csproj", "{27525BDD-2940-4CB2-8B76-395677997AEF}" @@ -21,10 +19,6 @@ Global {F389FFD8-B189-4A9F-A077-8D355BA23328}.Debug|Any CPU.Build.0 = Debug|Any CPU {F389FFD8-B189-4A9F-A077-8D355BA23328}.Release|Any CPU.ActiveCfg = Release|Any CPU {F389FFD8-B189-4A9F-A077-8D355BA23328}.Release|Any CPU.Build.0 = Release|Any CPU - {6CDB4320-1DC9-405A-97E8-272C9B4CD27C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {6CDB4320-1DC9-405A-97E8-272C9B4CD27C}.Debug|Any CPU.Build.0 = Debug|Any CPU - {6CDB4320-1DC9-405A-97E8-272C9B4CD27C}.Release|Any CPU.ActiveCfg = Release|Any CPU - {6CDB4320-1DC9-405A-97E8-272C9B4CD27C}.Release|Any CPU.Build.0 = Release|Any CPU {25F01564-0E35-471B-A9AC-C61C83AF3275}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {25F01564-0E35-471B-A9AC-C61C83AF3275}.Debug|Any CPU.Build.0 = Debug|Any CPU {25F01564-0E35-471B-A9AC-C61C83AF3275}.Release|Any CPU.ActiveCfg = Release|Any CPU diff --git a/Pulsar/Assets/EOCIcon-Presized.ico b/Pulsar/Assets/EOCIcon-Presized.ico deleted file mode 100644 index 6103052aad782b90b6ef4580b83597ab4e7185ae..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 215434 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

|0NQ{LF<>M9zkwxfKzpFp$H4`)|DAov52!c5 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 diff --git a/Pulsar/Global.Usings.cs b/Pulsar/Global.Usings.cs new file mode 100644 index 0000000..e289899 --- /dev/null +++ b/Pulsar/Global.Usings.cs @@ -0,0 +1,6 @@ +global using Pulsar; +global using Pulsar.Utils; +global using System.Text; +global using System.Text.Json; +global using System.Text.Json.Nodes; +global using System.Text.Json.Serialization; \ No newline at end of file diff --git a/Pulsar/LoggingUtils.cs b/Pulsar/LoggingUtils.cs index 719cb66..6f74ebb 100644 --- a/Pulsar/LoggingUtils.cs +++ b/Pulsar/LoggingUtils.cs @@ -1,5 +1,3 @@ -using System.Text; - namespace Pulsar; public static class LoggingUtils diff --git a/Pulsar/PluginManagement/PlaceholderPlugin.cs b/Pulsar/PluginManagement/PlaceholderPlugin.cs deleted file mode 100644 index 0145e2a..0000000 --- a/Pulsar/PluginManagement/PlaceholderPlugin.cs +++ /dev/null @@ -1,29 +0,0 @@ -using Observatory.Framework; - -namespace Pulsar.PluginManagement; - -public class PlaceholderPlugin : IObservatoryNotifier -{ - public PlaceholderPlugin(string name) - { - this.name = name; - } - - public string Name => name; - - private string name; - - public string ShortName => name; - - public string Version => string.Empty; - - public PluginUI PluginUI => new PluginUI(PluginUI.UIType.None, null); - - public object Settings { get => null; set { } } - - public void Load(IObservatoryCore observatoryCore) - { } - - public void OnNotificationEvent(NotificationArgs notificationArgs) - { } -} \ No newline at end of file diff --git a/Pulsar/PluginManagement/PluginCore.cs b/Pulsar/PluginManagement/PluginCore.cs deleted file mode 100644 index 3d85b2c..0000000 --- a/Pulsar/PluginManagement/PluginCore.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Reflection; -using Observatory.Framework; -using Observatory.Framework.Files; -using Pulsar.Utils; -using HttpClient = System.Net.Http.HttpClient; - -namespace Pulsar.PluginManagement; - -public class PluginCore : IObservatoryCore -{ - public string Version => Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0"; - - public Action GetPluginErrorLogger(IObservatoryPlugin plugin) - { - return (ex, context) => - { - LoggingUtils.LogError(ex, $"from plugin {plugin.ShortName} {context}"); - }; - } - - public Status GetStatus() => LogMonitor.GetInstance.Status; - - public Guid SendNotification(string title, string text) - { - return SendNotification(new NotificationArgs { Title = title, Detail = text }); - } - - public Guid SendNotification(NotificationArgs notificationArgs) - { - throw new NotImplementedException(); - } - - public void CancelNotification(Guid notificationId) - { - throw new NotImplementedException(); - } - - public void UpdateNotification(Guid id, NotificationArgs notificationArgs) - { - throw new NotImplementedException(); - } - - ///

- /// Adds an item to the datagrid on UI thread to ensure visual update. - /// - /// - /// - public void AddGridItem(IObservatoryWorker worker, object item) - { - worker.PluginUI.DataGrid.Add(item); - } - - public void AddGridItems(IObservatoryWorker worker, IEnumerable> items) - { - - } - - public void SetGridItems(IObservatoryWorker worker, IEnumerable> items) - { - - } - - - public HttpClient HttpClient - { - get => Utils.HttpClient.Client; - } - - public LogMonitorState CurrentLogMonitorState - { - get => LogMonitor.GetInstance.CurrentState; - } - - public bool IsLogMonitorBatchReading - { - get => LogMonitorStateChangedEventArgs.IsBatchRead(LogMonitor.GetInstance.CurrentState); - } - - public event EventHandler Notification; - - internal event EventHandler PluginMessage; - - public string PluginStorageFolder - { - get - { - throw new NotImplementedException(); - } - } - - public void SendPluginMessage(IObservatoryPlugin plugin, object message) - { - PluginMessage?.Invoke(this, new PluginMessageArgs(plugin.Name, plugin.Version, message)); - } -} \ No newline at end of file diff --git a/Pulsar/PluginManagement/PluginEventHandler.cs b/Pulsar/PluginManagement/PluginEventHandler.cs deleted file mode 100644 index 7984d06..0000000 --- a/Pulsar/PluginManagement/PluginEventHandler.cs +++ /dev/null @@ -1,192 +0,0 @@ -using System.Timers; -using Observatory.Framework; -using Observatory.Framework.Files; -using Observatory.Framework.Files.Journal; -using Pulsar.Utils; -using Timer = System.Timers.Timer; - -namespace Pulsar.PluginManagement; - -class PluginEventHandler -{ - private IEnumerable observatoryWorkers; - private IEnumerable observatoryNotifiers; - private HashSet disabledPlugins; - private List<(string error, string detail)> errorList; - private Timer timer; - - public PluginEventHandler(IEnumerable observatoryWorkers, IEnumerable observatoryNotifiers) - { - this.observatoryWorkers = observatoryWorkers; - this.observatoryNotifiers = observatoryNotifiers; - disabledPlugins = new(); - errorList = new(); - - InitializeTimer(); - } - - private void InitializeTimer() - { - // Use a timer to delay error reporting until incoming errors are "quiet" for one full second. - // Should resolve issue where repeated plugin errors open hundreds of error windows. - timer = new(); - timer.Interval = 1000; - timer.Elapsed += ReportErrorsIfAny; - } - - public void OnJournalEvent(object source, JournalEventArgs journalEventArgs) - { - foreach (var worker in observatoryWorkers) - { - if (disabledPlugins.Contains(worker)) continue; - try - { - worker.JournalEvent((JournalBase)journalEventArgs.journalEvent); - } - catch (PluginException ex) - { - RecordError(ex); - } - catch (Exception ex) - { - RecordError(ex, worker.Name, journalEventArgs.journalType.Name, ((JournalBase)journalEventArgs.journalEvent).Json); - } - ResetTimer(); - } - } - - public void OnStatusUpdate(object sourece, JournalEventArgs journalEventArgs) - { - foreach (var worker in observatoryWorkers) - { - if (disabledPlugins.Contains(worker)) continue; - try - { - worker.StatusChange((Status)journalEventArgs.journalEvent); - } - catch (PluginException ex) - { - RecordError(ex); - } - catch (Exception ex) - { - RecordError(ex, worker.Name, journalEventArgs.journalType.Name, ((JournalBase)journalEventArgs.journalEvent).Json); - } - ResetTimer(); - } - } - - internal void OnLogMonitorStateChanged(object sender, LogMonitorStateChangedEventArgs e) - { - foreach (var worker in observatoryWorkers) - { - if (disabledPlugins.Contains(worker)) continue; - try - { - worker.LogMonitorStateChanged(e); - } - catch (Exception ex) - { - RecordError(ex, worker.Name, "LogMonitorStateChanged event", ex.StackTrace ?? ""); - } - } - } - - public void OnNotificationEvent(object source, NotificationArgs notificationArgs) - { - foreach (var notifier in observatoryNotifiers) - { - if (disabledPlugins.Contains(notifier)) continue; - try - { - notifier.OnNotificationEvent(notificationArgs); - } - catch (PluginException ex) - { - RecordError(ex); - } - catch (Exception ex) - { - RecordError(ex, notifier.Name, notificationArgs.Title, notificationArgs.Detail); - } - ResetTimer(); - } - } - - public void OnPluginMessageEvent(object _, PluginMessageArgs messageArgs) - { - foreach (var plugin in observatoryNotifiers.Cast().Concat(observatoryWorkers)) - { - if (disabledPlugins.Contains(plugin)) continue; - - try - { - plugin.HandlePluginMessage(messageArgs.SourceName, messageArgs.SourceVersion, messageArgs.Message); - } - catch (PluginException ex) - { - RecordError(ex); - } - catch(Exception ex) - { - RecordError(ex, plugin.Name, "OnPluginMessageEvent event", ""); - } - } - } - - public void SetPluginEnabled(IObservatoryPlugin plugin, bool enabled) - { - if (enabled) disabledPlugins.Remove(plugin); - else disabledPlugins.Add(plugin); - } - - private void ResetTimer() - { - timer.Stop(); - try - { - timer.Start(); - } - catch - { - // Not sure why this happens, but I've reproduced it twice in a row after hitting - // read-all while also playing (ie. generating journals). - InitializeTimer(); - timer.Start(); - } - } - - private void RecordError(PluginException ex) - { - errorList.Add(($"Error in {ex.PluginName}: {ex.Message}", ex.StackTrace ?? "")); - } - - private void RecordError(Exception ex, string plugin, string eventType, string eventDetail) - { - errorList.Add(($"Error in {plugin} while handling {eventType}: {ex.Message}", eventDetail)); - } - - private void ReportErrorsIfAny(object sender, ElapsedEventArgs e) - { - if (errorList.Any()) - { - ErrorReporter.ShowErrorPopup($"Plugin Error{(errorList.Count > 1 ? "s" : "")}", errorList); - - timer.Stop(); - } - } -} - -internal class PluginMessageArgs -{ - internal string SourceName; - internal string SourceVersion; - internal object Message; - - internal PluginMessageArgs(string sourceName, string sourceVersion, object message) - { - SourceName = sourceName; - SourceVersion = sourceVersion; - Message = message; - } -} \ No newline at end of file diff --git a/Pulsar/PluginManagement/PluginManager.cs b/Pulsar/PluginManagement/PluginManager.cs deleted file mode 100644 index 08fb22e..0000000 --- a/Pulsar/PluginManagement/PluginManager.cs +++ /dev/null @@ -1,329 +0,0 @@ -using System.IO.Compression; -using System.Reflection; -using System.Runtime.Loader; -using Observatory.Framework; -using Pulsar.Utils; - -namespace Pulsar.PluginManagement; - -public class PluginManager -{ - public static PluginManager GetInstance - { - get - { - return _instance.Value; - } - } - - private static readonly Lazy _instance = new Lazy(NewPluginManager); - - private static PluginManager NewPluginManager() - { - return new PluginManager(); - } - - - public readonly List<(string error, string? detail)> errorList; - public readonly List<(IObservatoryWorker plugin, PluginStatus signed)> workerPlugins; - public readonly List<(IObservatoryNotifier plugin, PluginStatus signed)> notifyPlugins; - private readonly PluginCore core; - private readonly PluginEventHandler pluginHandler; - - private PluginManager() - { - errorList = LoadPlugins(out workerPlugins, out notifyPlugins); - - pluginHandler = new PluginEventHandler(workerPlugins.Select(p => p.plugin), notifyPlugins.Select(p => p.plugin)); - var logMonitor = LogMonitor.GetInstance; - - logMonitor.JournalEntry += pluginHandler.OnJournalEvent; - logMonitor.StatusUpdate += pluginHandler.OnStatusUpdate; - logMonitor.LogMonitorStateChanged += pluginHandler.OnLogMonitorStateChanged; - - core = new PluginCore(); - - List errorPlugins = new(); - - foreach (var plugin in workerPlugins.Select(p => p.plugin)) - { - try - { - LoadSettings(plugin); - plugin.Load(core); - } - catch (PluginException ex) - { - errorList.Add((FormatErrorMessage(ex), ex.StackTrace)); - errorPlugins.Add(plugin); - } - } - - workerPlugins.RemoveAll(w => errorPlugins.Contains(w.plugin)); - errorPlugins.Clear(); - - foreach (var plugin in notifyPlugins.Select(p => p.plugin)) - { - // Notifiers which are also workers need not be loaded again (they are the same instance). - if (!plugin.GetType().IsAssignableTo(typeof(IObservatoryWorker))) - { - try - { - LoadSettings(plugin); - plugin.Load(core); - } - catch (PluginException ex) - { - errorList.Add((FormatErrorMessage(ex), ex.StackTrace)); - errorPlugins.Add(plugin); - } - catch (Exception ex) - { - errorList.Add(($"{plugin.ShortName}: {ex.Message}", ex.StackTrace)); - errorPlugins.Add(plugin); - } - } - } - - notifyPlugins.RemoveAll(n => errorPlugins.Contains(n.plugin)); - - core.Notification += pluginHandler.OnNotificationEvent; - core.PluginMessage += pluginHandler.OnPluginMessageEvent; - - if (errorList.Any()) - ErrorReporter.ShowErrorPopup("Plugin Load Error" + (errorList.Count > 1 ? "s" : string.Empty), errorList); - } - - private static string FormatErrorMessage(PluginException ex) - { - return $"{ex.PluginName}: {ex.UserMessage}"; - } - - private void LoadSettings(IObservatoryPlugin plugin) - { - throw new NotImplementedException(); - } - - public static Dictionary GetSettingDisplayNames(object settings) - { - var settingNames = new Dictionary(); - - if (settings != null) - { - var properties = settings.GetType().GetProperties(); - foreach (var property in properties) - { - var attrib = property.GetCustomAttribute(); - if (attrib == null) - { - settingNames.Add(property, property.Name); - } - else - { - settingNames.Add(property, attrib.DisplayName); - } - } - } - return settingNames; - } - - public void SaveSettings(IObservatoryPlugin plugin, object settings) - { - throw new NotImplementedException(); - } - - public void SetPluginEnabled(IObservatoryPlugin plugin, bool enabled) - { - pluginHandler.SetPluginEnabled(plugin, enabled); - } - - private static List<(string, string?)> LoadPlugins(out List<(IObservatoryWorker plugin, PluginStatus signed)> observatoryWorkers, out List<(IObservatoryNotifier plugin, PluginStatus signed)> observatoryNotifiers) - { - observatoryWorkers = new(); - observatoryNotifiers = new(); - var errorList = new List<(string, string?)>(); - - var pluginPath = $"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins"; - - if (Directory.Exists(pluginPath)) - { - ExtractPlugins(pluginPath); - - var pluginLibraries = Directory.GetFiles($"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins", "*.dll"); - foreach (var dll in pluginLibraries) - { - try - { - var pluginStatus = PluginStatus.SigCheckDisabled; - var loadOkay = true; - - if (loadOkay) - { - var error = LoadPluginAssembly(dll, observatoryWorkers, observatoryNotifiers, pluginStatus); - if (!string.IsNullOrWhiteSpace(error)) - { - errorList.Add((error, string.Empty)); - } - } - } - catch (Exception ex) - { - errorList.Add(($"ERROR: {new FileInfo(dll).Name}, {ex.Message}", ex.StackTrace ?? string.Empty)); - LoadPlaceholderPlugin(dll, PluginStatus.InvalidLibrary, observatoryNotifiers); - } - } - } - return errorList; - } - - private static void ExtractPlugins(string pluginFolder) - { - var files = Directory.GetFiles(pluginFolder, "*.zip") - .Concat(Directory.GetFiles(pluginFolder, "*.eop")); // Elite Observatory Plugin - - foreach (var file in files) - { - try - { - ZipFile.ExtractToDirectory(file, pluginFolder, true); - File.Delete(file); - } - catch - { - // Just ignore files that don't extract successfully. - } - } - } - - private static string LoadPluginAssembly(string dllPath, List<(IObservatoryWorker plugin, PluginStatus signed)> workers, List<(IObservatoryNotifier plugin, PluginStatus signed)> notifiers, PluginStatus pluginStatus) - { - var recursionGuard = string.Empty; - - AssemblyLoadContext.Default.Resolving += (context, name) => { - - if ((name?.Name?.EndsWith("resources")).GetValueOrDefault(false)) - { - return null; - } - - // Importing Observatory.Framework in the Explorer Lua scripts causes an attempt to reload - // the assembly, just hand it back the one we already have. - if ((name?.Name?.StartsWith("Observatory.Framework")).GetValueOrDefault(false) || name?.Name == "ObservatoryFramework") - { - return context.Assemblies.Where(a => (a.FullName?.Contains("ObservatoryFramework")).GetValueOrDefault(false)).First(); - } - - var foundDlls = Directory.GetFileSystemEntries(new FileInfo($"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}deps").FullName, name.Name + ".dll", SearchOption.TopDirectoryOnly); - if (foundDlls.Any()) - { - return context.LoadFromAssemblyPath(foundDlls[0]); - } - - if (name.Name != recursionGuard && name.Name != null) - { - recursionGuard = name.Name; - return context.LoadFromAssemblyName(name); - } - - throw new Exception("Unable to load assembly " + name.Name); - }; - - var pluginAssembly = AssemblyLoadContext.Default.LoadFromAssemblyPath(new FileInfo(dllPath).FullName); - Type[] types; - var err = string.Empty; - var pluginCount = 0; - try - { - types = pluginAssembly.GetTypes(); - } - catch (ReflectionTypeLoadException ex) - { - types = ex.Types.OfType().ToArray(); - } - catch - { - types = Array.Empty(); - } - - IEnumerable workerTypes = types.Where(t => t.IsAssignableTo(typeof(IObservatoryWorker))); - foreach (var worker in workerTypes) - { - var constructor = worker.GetConstructor(Array.Empty()); - if (constructor != null) - { - var instance = constructor.Invoke(Array.Empty()); - workers.Add(((instance as IObservatoryWorker)!, pluginStatus)); - if (instance is IObservatoryNotifier) - { - // This is also a notifier; add to the notifier list as well, so the work and notifier are - // the same instance and can share state. - notifiers.Add(((instance as IObservatoryNotifier)!, pluginStatus)); - } - pluginCount++; - } - } - - // Filter out items which are also workers as we've already created them above. - var notifyTypes = types.Where(t => - t.IsAssignableTo(typeof(IObservatoryNotifier)) && !t.IsAssignableTo(typeof(IObservatoryWorker))); - foreach (var notifier in notifyTypes) - { - var constructor = notifier.GetConstructor(Array.Empty()); - if (constructor != null) - { - var instance = constructor.Invoke(Array.Empty()); - notifiers.Add(((instance as IObservatoryNotifier)!, pluginStatus)); - pluginCount++; - } - } - - if (pluginCount == 0) - { - err += $"ERROR: Library '{dllPath}' contains no suitable interfaces."; - LoadPlaceholderPlugin(dllPath, PluginStatus.InvalidPlugin, notifiers); - } - - return err; - } - - private static void LoadPlaceholderPlugin(string dllPath, PluginStatus pluginStatus, List<(IObservatoryNotifier plugin, PluginStatus signed)> notifiers) - { - PlaceholderPlugin placeholder = new(new FileInfo(dllPath).Name); - notifiers.Add((placeholder, pluginStatus)); - } - - /// - /// Possible plugin load results and signature statuses. - /// - public enum PluginStatus - { - /// - /// Plugin valid and signed with matching certificate. - /// - Signed, - /// - /// Plugin valid but not signed with any certificate. - /// - Unsigned, - /// - /// Plugin valid but not signed with valid certificate. - /// - InvalidSignature, - /// - /// Plugin invalid and cannot be loaded. Possible version mismatch. - /// - InvalidPlugin, - /// - /// Plugin not a CLR library. - /// - InvalidLibrary, - /// - /// Plugin valid but executing assembly has no certificate to match against. - /// - NoCert, - /// - /// Plugin signature checks disabled. - /// - SigCheckDisabled - } -} \ No newline at end of file diff --git a/Pulsar/Program.cs b/Pulsar/Program.cs index 1e98164..4ff2411 100644 --- a/Pulsar/Program.cs +++ b/Pulsar/Program.cs @@ -1,26 +1,21 @@ -using System.Reflection; -using Observatory; -using Pulsar; -using Pulsar.Utils; - -SettingsManager.Load(); - if (args.Length > 0 && File.Exists(args[0])) { var fileInfo = new FileInfo(args[0]); - if (fileInfo.Extension == ".eop" || fileInfo.Extension == ".zip") - File.Copy( - fileInfo.FullName, - $"{AppDomain.CurrentDomain.BaseDirectory}{Path.DirectorySeparatorChar}plugins{Path.DirectorySeparatorChar}{fileInfo.Name}"); + if (fileInfo.Extension is ".eop" or ".zip") + File.Copy(fileInfo.FullName, Path.Join(AppDomain.CurrentDomain.BaseDirectory, "plugins", fileInfo.Name)); } -var version = Assembly.GetEntryAssembly()?.GetName().Version?.ToString() ?? "0"; - try { - //TODO: Start Application + WebApplicationBuilder builder = WebApplication.CreateSlimBuilder(args); + + var app = builder.Build(); + + SettingsManager.Load(); + + await app.RunAsync(); } catch (Exception ex) { - LoggingUtils.LogError(ex, version); + LoggingUtils.LogError(ex, ""); } \ No newline at end of file diff --git a/Pulsar/Pulsar.csproj b/Pulsar/Pulsar.csproj index 6835446..d21ab34 100644 --- a/Pulsar/Pulsar.csproj +++ b/Pulsar/Pulsar.csproj @@ -6,6 +6,7 @@ enable enable Pulsar + latest diff --git a/Pulsar/Utils/CollectionExtesions.cs b/Pulsar/Utils/CollectionExtesions.cs new file mode 100644 index 0000000..fc27ed6 --- /dev/null +++ b/Pulsar/Utils/CollectionExtesions.cs @@ -0,0 +1,14 @@ +namespace Pulsar.Utils; + +public static class CollectionExtensions +{ + public static void Add(this ICollection<(T1,T2)> collection, T1 t1, T2 t2) + { + collection.Add((t1, t2)); + } + + public static void Add(this ICollection<(T1,T2,T3)> collection, T1 t1, T2 t2, T3 t3) + { + collection.Add((t1, t2, t3)); + } +} \ No newline at end of file diff --git a/Pulsar/Utils/ErrorReporter.cs b/Pulsar/Utils/ErrorReporter.cs index 6053475..49b93d6 100644 --- a/Pulsar/Utils/ErrorReporter.cs +++ b/Pulsar/Utils/ErrorReporter.cs @@ -1,10 +1,8 @@ -using System.Text; - -namespace Pulsar.Utils; +namespace Pulsar.Utils; public static class ErrorReporter { - public static void ShowErrorPopup(string title, List<(string error, string detail)> errorList) + public static void ShowErrorPopup(string title, List<(string error, JsonObject detail)> errorList) { // Limit number of errors displayed. StringBuilder displayMessage = new(); @@ -22,7 +20,7 @@ public static class ErrorReporter foreach (var error in errorList) { errorLog.AppendLine($"[{timestamp}]:"); - errorLog.AppendLine($"{error.error} - {error.detail}"); + errorLog.AppendLine($"{error.error} - {error.detail.ToJsonString()}"); errorLog.AppendLine(); } diff --git a/Pulsar/Utils/HttpClient.cs b/Pulsar/Utils/HttpClient.cs index 36351d5..fb75fb5 100644 --- a/Pulsar/Utils/HttpClient.cs +++ b/Pulsar/Utils/HttpClient.cs @@ -7,13 +7,7 @@ public sealed class HttpClient private static readonly Lazy lazy = new Lazy(() => new System.Net.Http.HttpClient()); - public static System.Net.Http.HttpClient Client - { - get - { - return lazy.Value; - } - } + public static System.Net.Http.HttpClient Client => lazy.Value; public static string GetString(string url) { diff --git a/Pulsar/Utils/JournalReader.cs b/Pulsar/Utils/JournalReader.cs index 7efa47a..588f3df 100644 --- a/Pulsar/Utils/JournalReader.cs +++ b/Pulsar/Utils/JournalReader.cs @@ -1,7 +1,4 @@ -using System.Reflection; -using System.Text; -using System.Text.Json; -using Observatory.Framework.Files.Journal; +using Observatory.Framework.Files.Journal; using Observatory.Framework.Files.Journal.Exploration; namespace Pulsar.Utils; @@ -10,85 +7,64 @@ public class JournalReader { public static TJournal ObservatoryDeserializer(string json) where TJournal : JournalBase { - TJournal deserialized; + TJournal deserialized; - if (typeof(TJournal) == typeof(InvalidJson)) + if (typeof(TJournal) == typeof(InvalidJson)) + { + InvalidJson invalidJson; + try { - InvalidJson invalidJson; - try - { - var reader = new Utf8JsonReader(Encoding.UTF8.GetBytes(json)); - var eventType = string.Empty; - var timestamp = string.Empty; + 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()) + while ((eventType == string.Empty || timestamp == string.Empty) && reader.Read()) + { + if (reader.TokenType == JsonTokenType.PropertyName) { - if (reader.TokenType == JsonTokenType.PropertyName) + if (reader.GetString() == "event") { - if (reader.GetString() == "event") - { - reader.Read(); - eventType = reader.GetString(); - } - else if (reader.GetString() == "timestamp") - { - reader.Read(); - timestamp = reader.GetString(); - } + reader.Read(); + eventType = reader.GetString(); + } + else if (reader.GetString() == "timestamp") + { + reader.Read(); + timestamp = reader.GetString(); } } - - invalidJson = new InvalidJson - { - Event = "InvalidJson", - Timestamp = timestamp, - OriginalEvent = eventType - }; } - catch + + invalidJson = new InvalidJson { - invalidJson = new InvalidJson - { - Event = "InvalidJson", - Timestamp = string.Empty, - OriginalEvent = "Invalid" - }; - } - - deserialized = (TJournal)Convert.ChangeType(invalidJson, typeof(TJournal)); - + Event = "InvalidJson", + Timestamp = DateTimeOffset.UnixEpoch, + OriginalEvent = eventType + }; } - //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")) + catch { - deserialized = JsonSerializer.Deserialize(json.Replace("\"RotationPeriod\":inf,", "")); + invalidJson = new InvalidJson + { + Event = "InvalidJson", + Timestamp = DateTimeOffset.UnixEpoch, + OriginalEvent = "Invalid" + }; } - else - { - deserialized = JsonSerializer.Deserialize(json); - } - deserialized.Json = json; - return deserialized; + 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); } - - public static Dictionary PopulateEventClasses() - { - var eventClasses = new Dictionary(StringComparer.InvariantCultureIgnoreCase); - - var allTypes = Assembly.GetAssembly(typeof(JournalBase)).GetTypes(); - - var journalTypes = allTypes.Where(a => a.IsSubclassOf(typeof(JournalBase))); - - foreach (var journalType in journalTypes) - { - eventClasses.Add(journalType.Name, journalType); - } - - eventClasses.Add("JournalBase", typeof(JournalBase)); - - return eventClasses; - } + return deserialized; + } } \ No newline at end of file diff --git a/Pulsar/Utils/LogMonitor.cs b/Pulsar/Utils/LogMonitor.cs index fad9718..cef80ba 100644 --- a/Pulsar/Utils/LogMonitor.cs +++ b/Pulsar/Utils/LogMonitor.cs @@ -5,184 +5,184 @@ using Observatory.Framework.Files.Journal; namespace Pulsar.Utils; +using JournalEvent = (Exception ex, string file, JsonObject line); + class LogMonitor { #region Singleton Instantiation - public static LogMonitor GetInstance - { - get - { - return _instance.Value; - } - } + public static LogMonitor GetInstance => _instance.Value; private static readonly Lazy _instance = new(NewLogMonitor); private static LogMonitor NewLogMonitor() { - return new LogMonitor(); - } + return new LogMonitor(); + } private LogMonitor() { - currentLine = new(); - journalTypes = JournalReader.PopulateEventClasses(); - InitializeWatchers(string.Empty); - SetLogMonitorState(LogMonitorState.Idle); - } + currentLine = new(); + InitializeWatchers(string.Empty); + SetLogMonitorState(LogMonitorState.Idle); + } #endregion #region Public properties - public LogMonitorState CurrentState - { - get => currentState; - } + + public LogMonitorState CurrentState => currentState; public Status Status { get; private set; } - + #endregion #region Public Methods public void Start() { - if (firstStartMonitor) - { - // Only pre-read on first start monitor. Beyond that it's simply pause/resume. - firstStartMonitor = false; - PrereadJournals(); - } - journalWatcher.EnableRaisingEvents = true; - statusWatcher.EnableRaisingEvents = true; - SetLogMonitorState(LogMonitorState.Realtime); - JournalPoke(); + if (firstStartMonitor) + { + // Only pre-read on first start monitor. Beyond that it's simply pause/resume. + firstStartMonitor = false; + PrereadJournals(); } + journalWatcher.EnableRaisingEvents = true; + statusWatcher.EnableRaisingEvents = true; + SetLogMonitorState(LogMonitorState.Realtime); + JournalPoke(); + } + public void Stop() { - journalWatcher.EnableRaisingEvents = false; - statusWatcher.EnableRaisingEvents = false; - SetLogMonitorState(LogMonitorState.Idle); - } + journalWatcher.EnableRaisingEvents = false; + statusWatcher.EnableRaisingEvents = false; + SetLogMonitorState(LogMonitorState.Idle); + } public void ChangeWatchedDirectory(string path) { - journalWatcher.Dispose(); - statusWatcher.Dispose(); - InitializeWatchers(path); - } + journalWatcher.Dispose(); + statusWatcher.Dispose(); + InitializeWatchers(path); + } public bool IsMonitoring() { - return currentState.HasFlag(LogMonitorState.Realtime); - } + return currentState.HasFlag(LogMonitorState.Realtime); + } // TODO(fredjk_gh): Remove? public bool ReadAllInProgress() { - return LogMonitorStateChangedEventArgs.IsBatchRead(currentState); - } + return LogMonitorStateChangedEventArgs.IsBatchRead(currentState); + } public Func> ReadAllGenerator(out int fileCount) { - // Prevent pre-reading when starting monitoring after reading all. - firstStartMonitor = false; - SetLogMonitorState(currentState | LogMonitorState.Batch); + // Prevent pre-reading when starting monitoring after reading all. + firstStartMonitor = false; + SetLogMonitorState(currentState | LogMonitorState.BatchProcessing); - var logDirectory = GetJournalFolder(); - var files = GetJournalFilesOrdered(logDirectory); - fileCount = files.Count(); + var logDirectory = GetJournalFolder(); + var files = GetJournalFilesOrdered(logDirectory); + fileCount = files.Count(); - IEnumerable ReadAllJournals() + IEnumerable ReadAllJournals() + { + var readErrors = new List(); + foreach (var file in files) { - var readErrors = new List<(Exception ex, string file, string line)>(); - foreach (var file in files) - { - yield return file.Name; - readErrors.AddRange( - ProcessLines(ReadAllLines(file.FullName), file.Name)); - } + yield return file.Name; + readErrors.AddRange(ProcessJournal(ReadByLines(file.FullName), file.Name)); + } - ReportErrors(readErrors); - SetLogMonitorState(currentState & ~LogMonitorState.Batch); - }; - - return ReadAllJournals; + ReportErrors(readErrors); + SetLogMonitorState(currentState & ~LogMonitorState.BatchProcessing); } + ; + + return ReadAllJournals; + } + public void PrereadJournals() { - SetLogMonitorState(currentState | LogMonitorState.PreRead); + SetLogMonitorState(currentState | LogMonitorState.Init); - var logDirectory = GetJournalFolder(); - var files = GetJournalFilesOrdered(logDirectory).ToList(); + var logDirectory = GetJournalFolder(); + var files = GetJournalFilesOrdered(logDirectory).ToList(); - // Read at most the last two files (in case we were launched after the game and the latest - // journal is mostly empty) but keeping only the lines since the last FSDJump. - List lastSystemLines = new(); - List lastFileLines = new(); - List fileHeaderLines = new(); - var sawFSDJump = false; - foreach (var file in files.Skip(Math.Max(files.Count - 2, 0))) + // Read at most the last two files (in case we were launched after the game and the latest + // journal is mostly empty) but keeping only the lines since the last FSDJump. + //TODO: strongly type these + List lastSystemLines = new(); + List lastFileLines = new(); + List fileHeaderLines = new(); + var sawFSDJump = false; + foreach (var file in files.Skip(Math.Max(files.Count - 2, 0))) + { + var lines = ReadByLines(file.FullName); + foreach (var line in lines) { - var lines = ReadAllLines(file.FullName); - foreach (var line in lines) + var eventType = JournalUtilities.GetEventType(line); + if (eventType == "FSDJump" || eventType == "CarrierJump" && + ((line["Docked"]?.GetValue() ?? false) || + (line["OnFoot"]?.GetValue() ?? false))) { - var eventType = JournalUtilities.GetEventType(line); - if (eventType.Equals("FSDJump") || (eventType.Equals("CarrierJump") && (line.Contains("\"Docked\":true") || line.Contains("\"OnFoot\":true")))) - { - // Reset, start collecting again. - lastSystemLines.Clear(); - sawFSDJump = true; - } - else if (eventType.Equals("Fileheader")) - { - lastFileLines.Clear(); - fileHeaderLines.Clear(); - fileHeaderLines.Add(line); - } - else if (eventType.Equals("LoadGame") || eventType.Equals("Statistics")) - { - // A few header lines to collect. - fileHeaderLines.Add(line); - } - lastSystemLines.Add(line); - lastFileLines.Add(line); + // Reset, start collecting again. + lastSystemLines.Clear(); + sawFSDJump = true; } - } - - // If we didn't see a jump in the recent logs (Cmdr is stationary in a system for a while - // ie. deep-space mining from a carrier), at very least, read from the beginning of the - // current journal file which includes the important stuff like the last "LoadGame", etc. This - // also helps out in cases where one forgets to hit "Start Monitor" until part-way into the - // session (if auto-start is not enabled). - List linesToRead = lastFileLines; - if (sawFSDJump) - { - // If we saw any relevant header lines, insert them as well. This ensures odyssey biologicials are properly - // counted/presented, current Commander name is present, etc. - if (fileHeaderLines.Count > 0) + else if (eventType == "Fileheader") { - lastSystemLines.InsertRange(0, fileHeaderLines); + lastFileLines.Clear(); + fileHeaderLines.Clear(); + fileHeaderLines.Add(line); + } + else if (eventType == "LoadGame" || eventType == "Statistics") + { + // A few header lines to collect. + fileHeaderLines.Add(line); } - linesToRead = lastSystemLines; - } - ReportErrors(ProcessLines(linesToRead, "Pre-read")); - SetLogMonitorState(currentState & ~LogMonitorState.PreRead); + lastSystemLines.Add(line); + lastFileLines.Add(line); + } } + // If we didn't see a jump in the recent logs (Cmdr is stationary in a system for a while + // ie. deep-space mining from a carrier), at very least, read from the beginning of the + // current journal file which includes the important stuff like the last "LoadGame", etc. This + // also helps out in cases where one forgets to hit "Start Monitor" until part-way into the + // session (if auto-start is not enabled). + var linesToRead = lastFileLines; + if (sawFSDJump) + { + // If we saw any relevant header lines, insert them as well. This ensures odyssey biologicials are properly + // counted/presented, current Commander name is present, etc. + if (fileHeaderLines.Count > 0) + { + lastSystemLines.InsertRange(0, fileHeaderLines); + } + + linesToRead = lastSystemLines; + } + + ReportErrors(ProcessJournal(linesToRead, "Pre-read")); + SetLogMonitorState(currentState & ~LogMonitorState.Init); + } + #endregion #region Public Events - public event EventHandler LogMonitorStateChanged; + public event EventHandler? LogMonitorStateChanged; - public event EventHandler JournalEntry; + public event EventHandler? JournalEntry; - public event EventHandler StatusUpdate; + public event EventHandler? StatusUpdate; #endregion @@ -194,17 +194,19 @@ class LogMonitor private readonly Dictionary currentLine; private LogMonitorState currentState = LogMonitorState.Idle; // Change via #SetLogMonitorState private bool firstStartMonitor = true; - private readonly string[] EventsWithAncillaryFile = { - "Cargo", - "NavRoute", - "Market", - "Outfitting", - "Shipyard", - "Backpack", - "FCMaterials", - "ModuleInfo", - "ShipLocker" - }; + + private readonly string[] EventsWithAncillaryFile = + { + "Cargo", + "NavRoute", + "Market", + "Outfitting", + "Shipyard", + "Backpack", + "FCMaterials", + "ModuleInfo", + "ShipLocker" + }; #endregion @@ -212,203 +214,190 @@ class LogMonitor private void SetLogMonitorState(LogMonitorState newState) { - var oldState = currentState; - currentState = newState; - LogMonitorStateChanged?.Invoke(this, new LogMonitorStateChangedEventArgs - { - PreviousState = oldState, - NewState = newState - }); ; + var oldState = currentState; + currentState = newState; + LogMonitorStateChanged?.Invoke(this, new LogMonitorStateChangedEventArgs + { + PreviousState = oldState, + NewState = newState + }); + ; - Debug.WriteLine("LogMonitor State change: {0} -> {1}", oldState, newState); - } + Debug.WriteLine("LogMonitor State change: {0} -> {1}", oldState, newState); + } private void InitializeWatchers(string path) { - var logDirectory = GetJournalFolder(); + var logDirectory = GetJournalFolder(); - journalWatcher = new FileSystemWatcher(logDirectory, "Journal.*.??.log") - { - NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | - NotifyFilters.FileName | NotifyFilters.CreationTime - }; - journalWatcher.Changed += LogChangedEvent; - journalWatcher.Created += LogCreatedEvent; + journalWatcher = new FileSystemWatcher(logDirectory, "Journal.*.??.log") + { + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | + NotifyFilters.FileName | NotifyFilters.CreationTime + }; + journalWatcher.Changed += LogChangedEvent; + journalWatcher.Created += LogCreatedEvent; - statusWatcher = new FileSystemWatcher(logDirectory, "Status.json") - { - NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size - }; - statusWatcher.Changed += StatusUpdateEvent; - } + statusWatcher = new FileSystemWatcher(logDirectory, "Status.json") + { + NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size + }; + statusWatcher.Changed += StatusUpdateEvent; + } private static string GetJournalFolder() { - throw new NotImplementedException(); + throw new NotImplementedException(); + } + + private List ProcessJournal(IEnumerable lines, string file) + { + var readErrors = new List<(Exception ex, string file, JsonObject line)>(); + foreach (var line in lines) + { + try + { + DeserializeAndInvoke(line); + } + catch (Exception ex) + { + readErrors.Add(ex, file, line); + } } - private List<(Exception ex, string file, string line)> ProcessLines(List lines, string file) + return readErrors; + } + + + private void DeserializeAndInvoke(JsonObject? line) { - var readErrors = new List<(Exception ex, string file, string line)>(); - foreach (var line in lines) + var eventType = JournalUtilities.GetEventType(line); + + var journalEvent = new JournalEventArgs(eventType, line); + + JournalEntry?.Invoke(this, journalEvent); + + // Files are only valid if realtime, otherwise they will be stale or empty. + if (!currentState.HasFlag(LogMonitorState.BatchProcessing) && EventsWithAncillaryFile.Contains(eventType)) + { + HandleModuleInfoFile(eventType); + } + } + + private async Task HandleModuleInfoFile(string eventType) + { + var filename = eventType == "ModuleInfo" + ? "ModulesInfo.json" // Just FDev things + : eventType + ".json"; + + // I have no idea what order Elite writes these files or if they're already written + // by the time the journal updates. + // Brief sleep to ensure the content is updated before we read it. + + // Some files are still locked by another process after 50ms. + // Retry every 50ms for 0.5 seconds before giving up. + + JsonObject? fileContent = null; + var retryCount = 0; + + while (fileContent == null && retryCount < 10) + { + await Task.Delay(TimeSpan.FromSeconds(0.5)); + try { - try + fileContent = ReadFile(Path.Join(journalWatcher.Path, filename)); + var fileObject = new JournalEventArgs(eventType, fileContent); + JournalEntry?.Invoke(this, fileObject); + } + catch + { + retryCount++; + } + } + } + + private static void ReportErrors(List readErrors) + { + if (readErrors.Any()) + { + var errorList = readErrors.Select(error => + { + string message; + if (error.ex.InnerException == null) { - DeserializeAndInvoke(line); + message = error.ex.Message; } - catch (Exception ex) + else { - readErrors.Add((ex, file, line)); + message = error.ex.InnerException.Message; } - } - return readErrors; - } - - private JournalEventArgs DeserializeToEventArgs(string eventType, string line) - { - - var eventClass = journalTypes[eventType]; - var journalRead = typeof(JournalReader).GetMethod(nameof(JournalReader.ObservatoryDeserializer)); - var journalGeneric = journalRead.MakeGenericMethod(eventClass); - var entry = journalGeneric.Invoke(null, new object[] { line }); - return new JournalEventArgs { journalType = eventClass, journalEvent = entry }; - } - - private void DeserializeAndInvoke(string line) - { - var eventType = JournalUtilities.GetEventType(line); - if (!journalTypes.ContainsKey(eventType)) - { - eventType = "JournalBase"; - } - - var journalEvent = DeserializeToEventArgs(eventType, line); - - JournalEntry?.Invoke(this, journalEvent); - - // Files are only valid if realtime, otherwise they will be stale or empty. - if (!currentState.HasFlag(LogMonitorState.Batch) && EventsWithAncillaryFile.Contains(eventType)) - { - HandleAncillaryFile(eventType); - } - } - - private void HandleAncillaryFile(string eventType) - { - var filename = eventType == "ModuleInfo" - ? "ModulesInfo.json" // Just FDev things - : eventType + ".json"; - - // I have no idea what order Elite writes these files or if they're already written - // by the time the journal updates. - // Brief sleep to ensure the content is updated before we read it. - - // Some files are still locked by another process after 50ms. - // Retry every 50ms for 0.5 seconds before giving up. - - string fileContent = null; - var retryCount = 0; - - while (fileContent == null && retryCount < 10) - { - Thread.Sleep(50); - try - { - using var fileStream = File.Open(journalWatcher.Path + Path.DirectorySeparatorChar + filename, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - using var reader = new StreamReader(fileStream); - fileContent = reader.ReadToEnd(); - var fileObject = DeserializeToEventArgs(eventType + "File", fileContent); - JournalEntry?.Invoke(this, fileObject); - } - catch - { - retryCount++; - } - } - } - - private static void ReportErrors(List<(Exception ex, string file, string line)> readErrors) - { - if (readErrors.Any()) - { - var errorList = readErrors.Select(error => - { - string message; - if (error.ex.InnerException == null) - { - message = error.ex.Message; - } - else - { - message = error.ex.InnerException.Message; - } - return ($"Error reading file {error.file}: {message}", error.line); - }); - - ErrorReporter.ShowErrorPopup($"Journal Read Error{(readErrors.Count > 1 ? "s" : "")}", errorList.ToList()); - - } + + return ($"Error reading file {error.file}: {message}", error.line); + }); + + ErrorReporter.ShowErrorPopup($"Journal Read Error{(readErrors.Count > 1 ? "s" : "")}", errorList.ToList()); } + } private void LogChangedEvent(object source, FileSystemEventArgs eventArgs) { - var fileContent = ReadAllLines(eventArgs.FullPath); + var fileContent = ReadByLines(eventArgs.FullPath); - if (!currentLine.ContainsKey(eventArgs.FullPath)) - { - currentLine.Add(eventArgs.FullPath, fileContent.Count - 1); - } - - foreach (var line in fileContent.Skip(currentLine[eventArgs.FullPath])) - { - try - { - DeserializeAndInvoke(line); - } - catch (Exception ex) - { - ReportErrors(new List<(Exception ex, string file, string line)> { (ex, eventArgs.Name ?? string.Empty, line) }); - } - } - - currentLine[eventArgs.FullPath] = fileContent.Count; + if (!currentLine.ContainsKey(eventArgs.FullPath)) + { + currentLine.Add(eventArgs.FullPath, fileContent.Count() - 1); } - private static List ReadAllLines(string path) - { - var lines = new List(); + foreach (var line in fileContent.Skip(currentLine[eventArgs.FullPath])) + { try { - using StreamReader file = new(File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite)); - while (!file.EndOfStream) - { - lines.Add(file.ReadLine() ?? string.Empty); - } + DeserializeAndInvoke(line); } - catch (IOException ioEx) + catch (Exception ex) { - ReportErrors(new List<(Exception, string, string)> { (ioEx, path, "") }); + ReportErrors([(ex, eventArgs.Name ?? string.Empty, line)]); } - return lines; } + currentLine[eventArgs.FullPath] = fileContent.Count(); + } + + private static IEnumerable ReadByLines(string path) + { + const int bufferSize = 512; + using var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var reader = new StreamReader(file, Encoding.UTF8, true, bufferSize); + while (reader.ReadLine() is { } line + && JsonNode.Parse(line) is { } parsed) + { + yield return parsed.AsObject(); + } + } + + private static JsonObject? ReadFile(string path) + { + using var file = File.Open(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + return JsonNode.Parse(file)?.AsObject(); + } + private void LogCreatedEvent(object source, FileSystemEventArgs eventArgs) { - currentLine.Add(eventArgs.FullPath, 0); - LogChangedEvent(source, eventArgs); - } + currentLine.Add(eventArgs.FullPath, 0); + LogChangedEvent(source, eventArgs); + } private void StatusUpdateEvent(object source, FileSystemEventArgs eventArgs) { - var handler = StatusUpdate; - var statusLines = ReadAllLines(eventArgs.FullPath); - if (statusLines.Count > 0) - { - var status = JournalReader.ObservatoryDeserializer(statusLines[0]); - Status = status; - handler?.Invoke(this, new JournalEventArgs { journalType = typeof(Status), journalEvent = status }); - } + var handler = StatusUpdate; + var statusLines = ReadFile(eventArgs.FullPath); + if (statusLines != null) + { + var status = statusLines.Deserialize(); + Status = status; + handler(this, new JournalEventArgs("Status", statusLines)); } + } /// /// Touches most recent journal file once every 250ms while LogMonitor is monitoring. @@ -416,36 +405,38 @@ class LogMonitor /// private async void JournalPoke() { - var journalFolder = GetJournalFolder(); + var journalFolder = GetJournalFolder(); - await Task.Run(() => + await Task.Run(() => + { + while (IsMonitoring()) { - while (IsMonitoring()) - { - var journals = GetJournalFilesOrdered(journalFolder); + var journals = GetJournalFilesOrdered(journalFolder); - if (journals.Any()) - { - var fileToPoke = GetJournalFilesOrdered(journalFolder).Last(); - using var stream = fileToPoke.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); - stream.Close(); - } - Thread.Sleep(250); + if (journals.Any()) + { + var fileToPoke = GetJournalFilesOrdered(journalFolder).Last(); + using var stream = fileToPoke.Open(FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + stream.Close(); } - }); - } + + Thread.Sleep(250); + } + }); + } private static string GetSavedGamesPath() { - throw new NotImplementedException(); - } + throw new NotImplementedException(); + } private static IEnumerable GetJournalFilesOrdered(string path) { - var journalFolder = new DirectoryInfo(path); - return from file in journalFolder.GetFiles("Journal.*.??.log") - orderby file.LastWriteTime - select file; - } + var journalFolder = new DirectoryInfo(path); + return from file in journalFolder.GetFiles("Journal.*.??.log") + orderby file.LastWriteTime + select file; + } + #endregion } \ No newline at end of file