Created
January 26, 2016 16:11
-
-
Save MagicMau/8bffee3c2d2c2f7d6c56 to your computer and use it in GitHub Desktop.
A FileSystemWatcher that can parse Elite's verbose log files
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Diagnostics; | |
using System.IO; | |
using System.Linq; | |
using System.Text; | |
using System.Text.RegularExpressions; | |
using System.Threading; | |
using System.Threading.Tasks; | |
namespace EliteLogReader | |
{ | |
/// <summary> | |
/// <see cref="https://github.com/hastarin/HAST-Elite-Assistant/blob/master/HAST.Elite.Dangerous.DataAssistant/ViewModels/LogWatcher.cs"/> | |
/// </summary> | |
public class LogWatcher : FileSystemWatcher | |
{ | |
public delegate void SystemChangedEventHandler(object sender, SystemChangedEventArgs e); | |
public event SystemChangedEventHandler OnSystemChanged; | |
/// <summary> | |
/// The default filter | |
/// </summary> | |
private const string DefaultFilter = @"netLog.*.log"; | |
/// <summary> | |
/// The system line regex | |
/// </summary> | |
private readonly Regex systemLineRegex = new Regex( | |
@"^\{[0-9:]{8}\}\sSystem:\d+\((?<system>[^)]+)\).* (?<flightType>[^ ]*)$", | |
RegexOptions.Multiline | RegexOptions.CultureInvariant | RegexOptions.Compiled); | |
/// <summary> | |
/// The current system. | |
/// </summary> | |
private string currentSystem = string.Empty; | |
/// <summary> | |
/// The last offset used when reading the netLog file. | |
/// </summary> | |
private long lastOffset; | |
/// <summary> | |
/// The latest log file | |
/// </summary> | |
private string latestLogFile; | |
private Thread logThread = null; | |
private int logThreadId = 0; | |
private bool isWatching = false; | |
private Timer timer = null; | |
/// <summary> | |
/// Initializes a new instance of the <see cref="T:System.Object" /> class. | |
/// </summary> | |
public LogWatcher(string path) | |
{ | |
this.Filter = DefaultFilter; | |
this.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.CreationTime | NotifyFilters.Size; | |
try | |
{ | |
this.Path = path; | |
} | |
catch (Exception) | |
{ | |
} | |
} | |
/// <summary> | |
/// Gets or sets the current system. | |
/// </summary> | |
public string CurrentSystem | |
{ | |
get | |
{ | |
return currentSystem; | |
} | |
set | |
{ | |
if (currentSystem == value) | |
return; | |
currentSystem = value; | |
var handler = OnSystemChanged; | |
if (handler != null) | |
{ | |
handler(this, new SystemChangedEventArgs(value)); | |
} | |
} | |
} | |
public FlightTypes CurrentFlightType { get; private set; } | |
/// <summary> | |
/// Gets or sets the latest log file. | |
/// </summary> | |
public string LatestLogFile | |
{ | |
get | |
{ | |
return this.latestLogFile; | |
} | |
set | |
{ | |
this.latestLogFile = value; | |
} | |
} | |
/// <summary> | |
/// Determines whether the Path contains netLog files. | |
/// </summary> | |
/// <returns><c>true</c> if the Path contains netLog files; otherwise, <c>false</c>.</returns> | |
public bool IsValidPath() | |
{ | |
return this.IsValidPath(this.Path); | |
} | |
/// <summary> | |
/// Determines whether the specified path contains netLog files. | |
/// </summary> | |
/// <param name="path">The path.</param> | |
/// <returns><c>true</c> if the specified path contains netLog files; otherwise, <c>false</c>.</returns> | |
public bool IsValidPath(string path) | |
{ | |
var filesFound = false; | |
try | |
{ | |
filesFound = Directory.GetFiles(path, this.Filter).Any(); | |
} | |
catch (UnauthorizedAccessException) | |
{ | |
} | |
catch (ArgumentNullException) | |
{ | |
} | |
catch (DirectoryNotFoundException) | |
{ | |
} | |
catch (PathTooLongException) | |
{ | |
} | |
catch (ArgumentException) | |
{ | |
} | |
catch (IOException) | |
{ | |
} | |
return filesFound; | |
} | |
/// <summary> | |
/// Forces a refresh of the <see cref="LatestLogFile"/> and calls <see cref="CheckForSystemChange"/> | |
/// </summary> | |
public void Refresh() | |
{ | |
this.UpdateLatestLogFile(); | |
} | |
/// <summary> | |
/// Starts the watching. | |
/// </summary> | |
/// <exception cref="InvalidOperationException"> | |
/// Throws an exception if the <see cref="Path" /> does not contain netLogs | |
/// files. | |
/// </exception> | |
/// <exception cref="FileNotFoundException"> | |
/// The directory specified in <see cref="P:System.IO.FileSystemWatcher.Path" /> | |
/// could not be found. | |
/// </exception> | |
public void StartWatching() | |
{ | |
if (EnableRaisingEvents) | |
{ | |
// Already watching | |
return; | |
} | |
if (!this.IsValidPath()) | |
{ | |
//throw new InvalidOperationException( | |
// string.Format("Directory {0} does not contain netLog files?!", this.Path)); | |
return; // fail silently | |
} | |
isWatching = true; | |
UpdateLatestLogFile(); | |
Created += (sender, args) => UpdateLatestLogFile(); | |
Changed += LogWatcher_Changed; | |
EnableRaisingEvents = true; | |
} | |
private void LogWatcher_Changed(object sender, FileSystemEventArgs e) | |
{ | |
// if we're not watching anything, let's see if there is a log available | |
if (latestLogFile == null || e.Name != latestLogFile) | |
UpdateLatestLogFile(); | |
} | |
public void StopWatching() | |
{ | |
isWatching = false; | |
if (logThread != null) | |
logThread.Join(); | |
} | |
private void CheckForSystemChangeAsync() | |
{ | |
logThread = new Thread(ido => | |
{ | |
int id = (int)ido; | |
var logFile = System.IO.Path.Combine(Path, latestLogFile); | |
try | |
{ | |
using (StreamReader reader = new StreamReader(new FileStream(logFile, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))) | |
{ | |
while (id == logThreadId) | |
{ | |
for (int i = 0; isWatching && i < 50; i++) | |
{ | |
Thread.Sleep(100); | |
} | |
if (!isWatching || id != logThreadId) | |
return; | |
//if the file size has not changed, idle | |
if (reader.BaseStream.Length == lastOffset) | |
continue; | |
//seek to the last max offset | |
reader.BaseStream.Seek(lastOffset, SeekOrigin.Begin); | |
var newData = reader.ReadToEnd(); | |
var matches = this.systemLineRegex.Matches(newData); | |
if (matches.Count > 0) | |
{ | |
var lastSystemFound = matches[matches.Count - 1].Groups["system"].Value; | |
var flightTypeText = matches[matches.Count - 1].Groups["flightType"].Value; | |
FlightTypes flightType; | |
if (!Enum.TryParse(flightTypeText, out flightType)) | |
flightType = FlightTypes.Unknown; // use by default | |
CurrentFlightType = flightType; | |
if (this.currentSystem != lastSystemFound) | |
{ | |
this.CurrentSystem = lastSystemFound; | |
} | |
} | |
//update the last max offset | |
lastOffset = reader.BaseStream.Position; | |
} | |
} | |
} | |
catch | |
{ | |
// Something went wrong, let's check log files again | |
latestLogFile = null; | |
} | |
if (id == logThreadId) | |
{ | |
// We're here, so something must've gone wrong | |
// Let's try again in a few seconds | |
Thread.Sleep(5000); | |
UpdateLatestLogFile(); | |
} | |
}); | |
logThread.IsBackground = true; | |
logThread.Start(++logThreadId); | |
} | |
/// <summary> | |
/// Updates the <see cref="LatestLogFile" /> property. | |
/// </summary> | |
private void UpdateLatestLogFile() | |
{ | |
var netLogs = Directory.GetFiles(this.Path, "netLog*"); | |
if (netLogs.Length == 0 && timer == null) | |
{ | |
// schedule a check for sometime in the future | |
timer = new Timer(_ => UpdateLatestLogFile(), null, 5000, Timeout.Infinite); | |
return; | |
} | |
var latestNetlog = netLogs.OrderByDescending(f => f).First(); | |
if (this.latestLogFile != latestNetlog) | |
{ | |
this.lastOffset = 0; | |
this.LatestLogFile = latestNetlog; | |
this.CheckForSystemChangeAsync(); | |
} | |
} | |
protected override void Dispose(bool disposing) | |
{ | |
base.Dispose(disposing); | |
if (disposing) | |
{ | |
if (timer != null) | |
{ | |
timer.Dispose(); | |
timer = null; | |
} | |
} | |
} | |
} | |
public class SystemChangedEventArgs : EventArgs | |
{ | |
public string SystemName { get; private set; } | |
public SystemChangedEventArgs(string systemName) | |
{ | |
SystemName = systemName; | |
} | |
} | |
public enum FlightTypes | |
{ | |
Unknown, | |
NormalFlight, | |
Supercruise | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment