AutoTrackR2/AutoTrackR2/LogHandler.cs
2025-04-06 17:59:18 -07:00

169 lines
No EOL
4.9 KiB
C#

using System.Diagnostics;
using System.IO;
using System.Text.RegularExpressions;
using AutoTrackR2.LogEventHandlers;
namespace AutoTrackR2;
// Represents a single log entry
// This is the object that will be passed to each handler, mostly for convenience
public class LogEntry
{
public DateTime Timestamp { get; set; }
public required string? Message { get; set; }
}
enum GameProcessState
{
NotRunning,
Running,
Unknown
}
public class LogHandler
{
private string _logPath;
private FileStream? _fileStream;
private StreamReader? _reader;
private Thread? _monitorThread;
private CancellationTokenSource? _cancellationTokenSource;
private GameProcessState _gameProcessState = GameProcessState.NotRunning;
private bool _isMonitoring = false;
public bool IsMonitoring => _isMonitoring;
// Handlers that should be run on every log entry
// Overlap with _startupEventHandlers is fine
private readonly List<ILogEventHandler> _eventHandlers = [
new LoginEvent(),
new InstancedInteriorEvent(),
new InArenaCommanderEvent(),
new InPersistentUniverseEvent(),
new GameVersionEvent(),
new JumpDriveStateChangedEvent(),
new RequestJumpFailedEvent()
];
public LogHandler(string? logPath)
{
if (string.IsNullOrEmpty(logPath))
{
throw new ArgumentNullException(nameof(logPath), "Log path cannot be null or empty");
}
_logPath = logPath;
}
public void Initialize()
{
if (!File.Exists(_logPath))
{
throw new FileNotFoundException("Log file not found", _logPath);
}
_fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_reader = new StreamReader(_fileStream);
while (_reader.ReadLine() is { } line)
{
HandleLogEntry(line);
}
// Ensures that any deaths already in log aren't sent to the APIs until the monitor thread is running
_eventHandlers.Add(new ActorDeathEvent());
StartMonitoring();
}
public void StartMonitoring()
{
if (_isMonitoring) return;
_cancellationTokenSource = new CancellationTokenSource();
_monitorThread = new Thread(() => MonitorLog(_cancellationTokenSource.Token));
_monitorThread.Start();
_isMonitoring = true;
}
public void StopMonitoring()
{
if (!_isMonitoring) return;
_cancellationTokenSource?.Cancel();
_monitorThread?.Join();
_reader?.Close();
_fileStream?.Close();
_isMonitoring = false;
}
// Parse a single line of the log file and run matching handlers
private void HandleLogEntry(string line)
{
// Console.WriteLine(line);
foreach (var handler in _eventHandlers)
{
var match = handler.Pattern.Match(line);
if (!match.Success) continue;
var entry = new LogEntry
{
Timestamp = DateTime.Now,
Message = line
};
handler.Handle(entry);
break;
}
}
private void MonitorLog(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
if (_reader == null || _fileStream == null)
{
break;
}
CheckGameProcessState();
List<string> lines = new List<string>();
while (_reader.ReadLine() is { } line)
{
lines.Add(line);
}
foreach (var line in lines)
{
// start new thread to handle log entry
var thread = new Thread(() => HandleLogEntry(line));
thread.Start();
// Console.WriteLine(line);
}
{
// Wait for new lines to be written to the log file
Thread.Sleep(1000);
}
}
Console.WriteLine("Monitor thread stopped");
}
private void CheckGameProcessState()
{
// Check if the game process is running by window name
var process = Process.GetProcesses().FirstOrDefault(p => p.MainWindowTitle == "Star Citizen");
GameProcessState newGameProcessState = process != null ? GameProcessState.Running : GameProcessState.NotRunning;
if (newGameProcessState == GameProcessState.Running && _gameProcessState == GameProcessState.NotRunning)
{
// Game process went from NotRunning to Running, so reload the Game.log file
Console.WriteLine("Game process started, reloading log file");
_reader?.Close();
_fileStream?.Close();
_fileStream = new FileStream(_logPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_reader = new StreamReader(_fileStream);
}
_gameProcessState = newGameProcessState;
}
}