2
0
mirror of https://github.com/9ParsonsB/Pulsar.git synced 2025-07-01 08:23:42 -04:00

Rework Journal File Reading

Remove Explorer
Remove Plugin Architecture
This commit is contained in:
2024-04-14 21:51:56 +10:00
parent c0c69dcdf7
commit 256ebb179e
42 changed files with 855 additions and 2807 deletions

View File

@ -0,0 +1,14 @@
namespace Pulsar.Utils;
public static class CollectionExtensions
{
public static void Add<T1, T2>(this ICollection<(T1,T2)> collection, T1 t1, T2 t2)
{
collection.Add((t1, t2));
}
public static void Add<T1, T2, T3>(this ICollection<(T1,T2,T3)> collection, T1 t1, T2 t2, T3 t3)
{
collection.Add((t1, t2, t3));
}
}

View File

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

View File

@ -7,13 +7,7 @@ public sealed class HttpClient
private static readonly Lazy<System.Net.Http.HttpClient> lazy = new Lazy<System.Net.Http.HttpClient>(() => 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)
{

View File

@ -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<TJournal>(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<TJournal>(json.Replace("\"RotationPeriod\":inf,", ""));
invalidJson = new InvalidJson
{
Event = "InvalidJson",
Timestamp = DateTimeOffset.UnixEpoch,
OriginalEvent = "Invalid"
};
}
else
{
deserialized = JsonSerializer.Deserialize<TJournal>(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<TJournal>(json.Replace("\"RotationPeriod\":inf,", ""));
}
else
{
deserialized = JsonSerializer.Deserialize<TJournal>(json);
}
public static Dictionary<string, Type> PopulateEventClasses()
{
var eventClasses = new Dictionary<string, Type>(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;
}
}

View File

@ -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<LogMonitor> _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<IEnumerable<string>> 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<string> ReadAllJournals()
IEnumerable<string> ReadAllJournals()
{
var readErrors = new List<JournalEvent>();
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<string> lastSystemLines = new();
List<string> lastFileLines = new();
List<string> 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<JsonObject?> lastSystemLines = new();
List<JsonObject?> lastFileLines = new();
List<JsonObject?> 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<bool>() ?? false) ||
(line["OnFoot"]?.GetValue<bool>() ?? 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<string> 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<LogMonitorStateChangedEventArgs> LogMonitorStateChanged;
public event EventHandler<LogMonitorStateChangedEventArgs>? LogMonitorStateChanged;
public event EventHandler<JournalEventArgs> JournalEntry;
public event EventHandler<JournalEventArgs>? JournalEntry;
public event EventHandler<JournalEventArgs> StatusUpdate;
public event EventHandler<JournalEventArgs>? StatusUpdate;
#endregion
@ -194,17 +194,19 @@ class LogMonitor
private readonly Dictionary<string, int> 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<JournalEvent> ProcessJournal(IEnumerable<JsonObject?> 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<string> 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<JournalEvent> 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<string> ReadAllLines(string path)
{
var lines = new List<string>();
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, "<reading all lines>") });
ReportErrors([(ex, eventArgs.Name ?? string.Empty, line)]);
}
return lines;
}
currentLine[eventArgs.FullPath] = fileContent.Count();
}
private static IEnumerable<JsonObject> 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<Status>(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 = status;
handler(this, new JournalEventArgs("Status", statusLines));
}
}
/// <summary>
/// Touches most recent journal file once every 250ms while LogMonitor is monitoring.
@ -416,36 +405,38 @@ class LogMonitor
/// </summary>
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<FileInfo> 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
}