Skip to content

Instantly share code, notes, and snippets.

@MagicMau
Created January 26, 2016 16:11
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MagicMau/8bffee3c2d2c2f7d6c56 to your computer and use it in GitHub Desktop.
Save MagicMau/8bffee3c2d2c2f7d6c56 to your computer and use it in GitHub Desktop.
A FileSystemWatcher that can parse Elite's verbose log files
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