From 1ce63c40fca170997e7e6964bf237d8c6876840e Mon Sep 17 00:00:00 2001
From: Fred Kuipers <mr.fredk@gmail.com>
Date: Wed, 30 Jun 2021 01:50:27 -0400
Subject: [PATCH] Implement "Pre-reading" of current system context when
 starting monitor

The first time you click "Start monitor" (assuming you haven't read-all), we read the last 2 log files to find the journal entries for the last system jumped into (if we can find the FSDJump entry) in order to re-populate the information in all interested plugins. Its much faster than reading all to restore current system context.

- For Botanist plugin, this will display what plants/planets you've already sampled and avoids placeholder planet entries with incorrect bio counts.
- For Explorer, it re-lists interesting items, in case you weren't done exploring the system.

See https://github.com/Xjph/ObservatoryCore/issues/5
---
 ObservatoryCore/LogMonitor.cs | 156 ++++++++++++++++++++++++----------
 1 file changed, 112 insertions(+), 44 deletions(-)

diff --git a/ObservatoryCore/LogMonitor.cs b/ObservatoryCore/LogMonitor.cs
index 962df8a..968802b 100644
--- a/ObservatoryCore/LogMonitor.cs
+++ b/ObservatoryCore/LogMonitor.cs
@@ -42,6 +42,12 @@ namespace Observatory
 
         public void Start()
         {
+            if (firstStarMonitor)
+            {
+                // Only pre-read on first start monitor. Beyond that it's simply pause/resume.
+                firstStarMonitor = false;
+                PrereadJournals();
+            }
             journalWatcher.EnableRaisingEvents = true;
             statusWatcher.EnableRaisingEvents = true;
             monitoring = true;
@@ -79,68 +85,67 @@ namespace Observatory
 
         public void ReadAllJournals(string path)
         {
+            // Prevent pre-reading when starting monitoring after reading all.
+            firstStarMonitor = false;
             readall = true;
             DirectoryInfo logDirectory = GetJournalFolder(path);
             var files = logDirectory.GetFiles("Journal.????????????.??.log");
             var readErrors = new List<(Exception ex, string file, string line)>();
             foreach (var file in files)
+            {
+                readErrors.AddRange(
+                    ProcessLines(ReadAllLines(file.FullName), file.Name));
+            }
+
+            ReportErrors(readErrors);
+            readall = false;
+        }
+
+        public void PrereadJournals()
+        {
+            // TODO: use the configured journal path, not the "default" detected path.
+            DirectoryInfo logDirectory = GetJournalFolder(String.Empty);
+            var files = logDirectory.GetFiles("Journal.????????????.??.log");
+
+            // 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<String> lastSystemLines = new();
+            string lastLoadGame = String.Empty;
+            bool sawFSDJump = false;
+            foreach (var file in files.Skip(Math.Max(files.Length - 2, 0)))
             {
                 var lines = ReadAllLines(file.FullName);
                 foreach (var line in lines)
                 {
-                    try
+                    var eventType = JournalUtilities.GetEventType(line);
+                    if (eventType.Equals("FSDJump"))
                     {
-                        DeserializeAndInvoke(line);
+                        // Reset, start collecting again.
+                        lastSystemLines.Clear();
+                        sawFSDJump = true;
                     }
-                    catch (Exception ex)
+                    else if (eventType.Equals("LoadGame"))
                     {
-                        readErrors.Add((ex, file.Name, line));
+                        lastLoadGame = line;
                     }
+                    lastSystemLines.Add(line);
                 }
             }
 
-            if (readErrors.Any())
+            // So we didn't see a jump in the recent logs. We could be re-logging, or something.
+            // Just bail on this attempt.
+            if (!sawFSDJump) return;
+
+            // If we saw a LoadGame, insert it as well. This ensures odyssey biologicials are properly
+            // counted/presented.
+            if (!String.IsNullOrEmpty(lastLoadGame))
             {
-                var errorContent = new System.Text.StringBuilder();
-                int count = 0;
-                foreach (var error in readErrors)
-                {
-                    errorContent.AppendLine(error.ex.InnerException.Message);
-                    errorContent.AppendLine($"File: {error.file}");
-                    if (error.line.Length > 200)
-                    {
-                        errorContent.AppendLine($"Line (first 200 chars): {error.line.Substring(0,200)}");
-                    }
-                    else
-                    {
-                        errorContent.AppendLine($"Line: {error.line}");
-                    }
-                    
-                    if (error != readErrors.Last())
-                    {
-                        errorContent.AppendLine();
-                        if (count++ == 5)
-                        {
-                            errorContent.AppendLine($"There are {readErrors.Count - 6} more errors but let's keep this window manageable.");
-                            break;
-                        }
-                    }
-                }
-                
-                if (Avalonia.Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop)
-                {
-                    var errorMessage = MessageBox.Avalonia.MessageBoxManager
-                    .GetMessageBoxStandardWindow(new MessageBox.Avalonia.DTO.MessageBoxStandardParams
-                    {
-                        ContentTitle = $"Journal Read Error{(readErrors.Count > 1 ? "s" : "")}",
-                        ContentMessage = errorContent.ToString()
-                    });
-                    errorMessage.ShowDialog(desktop.MainWindow);
-
-                }
-                
+                lastSystemLines.Insert(0, lastLoadGame);
             }
-            readall = false;
+
+            // We found an FSD jump, buffered the lines for that system (possibly including startup logs
+            // over a file boundary). Pump these through the plugins.
+            ReportErrors(ProcessLines(lastSystemLines, "Pre-read"));
         }
 
         #endregion
@@ -161,6 +166,7 @@ namespace Observatory
         private Dictionary<string, int> currentLine;
         private bool monitoring = false;
         private bool readall = false;
+        private bool firstStarMonitor = true;
 
         #endregion
 
@@ -226,6 +232,23 @@ namespace Observatory
             return logDirectory;
         }
 
+        private List<(Exception ex, string file, string line)> ProcessLines(List<String> lines, string file)
+        {
+            var readErrors = new List<(Exception ex, string file, string line)>();
+            foreach (var line in lines)
+            {
+                try
+                {
+                    DeserializeAndInvoke(line);
+                }
+                catch (Exception ex)
+                {
+                    readErrors.Add((ex, "Pre-read", line));
+                }
+            }
+            return readErrors;
+        }
+
         private void DeserializeAndInvoke(string line)
         {
             var eventType = JournalUtilities.GetEventType(line);
@@ -245,6 +268,51 @@ namespace Observatory
 
         }
 
+        private void ReportErrors(List<(Exception ex, string file, string line)> readErrors)
+        {
+            if (readErrors.Any())
+            {
+                var errorContent = new System.Text.StringBuilder();
+                int count = 0;
+                foreach (var error in readErrors)
+                {
+                    errorContent.AppendLine(error.ex.InnerException.Message);
+                    errorContent.AppendLine($"File: {error.file}");
+                    if (error.line.Length > 200)
+                    {
+                        errorContent.AppendLine($"Line (first 200 chars): {error.line.Substring(0, 200)}");
+                    }
+                    else
+                    {
+                        errorContent.AppendLine($"Line: {error.line}");
+                    }
+
+                    if (error != readErrors.Last())
+                    {
+                        errorContent.AppendLine();
+                        if (count++ == 5)
+                        {
+                            errorContent.AppendLine($"There are {readErrors.Count - 6} more errors but let's keep this window manageable.");
+                            break;
+                        }
+                    }
+                }
+
+                if (Avalonia.Application.Current.ApplicationLifetime is Avalonia.Controls.ApplicationLifetimes.IClassicDesktopStyleApplicationLifetime desktop)
+                {
+                    var errorMessage = MessageBox.Avalonia.MessageBoxManager
+                    .GetMessageBoxStandardWindow(new MessageBox.Avalonia.DTO.MessageBoxStandardParams
+                    {
+                        ContentTitle = $"Journal Read Error{(readErrors.Count > 1 ? "s" : "")}",
+                        ContentMessage = errorContent.ToString()
+                    });
+                    errorMessage.ShowDialog(desktop.MainWindow);
+
+                }
+
+            }
+        }
+
         private void LogChangedEvent(object source, FileSystemEventArgs eventArgs)
         {
             var fileContent = ReadAllLines(eventArgs.FullPath);