Skip to content

Commit

Permalink
Merge pull request Procurement-PoE#1042 from thailyn/log-file-monitor
Browse files Browse the repository at this point in the history
Automatically refresh used tab when log file changes
  • Loading branch information
Stickymaddness authored Aug 17, 2019
2 parents 42111ce + c942d6f commit 3cc6fe0
Show file tree
Hide file tree
Showing 5 changed files with 249 additions and 0 deletions.
2 changes: 2 additions & 0 deletions Procurement/Procurement.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,8 @@
<Compile Include="Controls\FragmentStashTab.xaml.cs">
<DependentUpon>FragmentStashTab.xaml</DependentUpon>
</Compile>
<Compile Include="Utility\ClientLogFileEventArgs.cs" />
<Compile Include="Utility\ClientLogFileWatcher.cs" />
<Compile Include="ViewModel\Filters\ForumExport\FossilFilter.cs" />
<Compile Include="ViewModel\Filters\ForumExport\FracturedItemFilter.cs" />
<Compile Include="ViewModel\Filters\ForumExport\IncubatorFilter.cs" />
Expand Down
18 changes: 18 additions & 0 deletions Procurement/Utility/ClientLogFileEventArgs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
using System;

namespace Procurement.Utility
{
public class ClientLogFileEventArgs : EventArgs
{
public DateTime EventDateTime { get; private set; }
public long EventTimestamp { get; private set; }
public string LocationEntered { get; private set; }

public ClientLogFileEventArgs(DateTime eventDateTime, long eventTimestamp, string locationEntered)
{
EventDateTime = eventDateTime;
EventTimestamp = eventTimestamp;
LocationEntered = locationEntered;
}
}
}
188 changes: 188 additions & 0 deletions Procurement/Utility/ClientLogFileWatcher.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,188 @@
using POEApi.Infrastructure;
using POEApi.Model;
using System;
using System.IO;
using System.Linq;
using System.Text.RegularExpressions;

namespace Procurement.Utility
{
class ClientLogFileWatcher
{
private const string _clientLogFileLocationConfigName = "ClientLogFileLocation";
private const string _enableClientLogFileMonitoringConfigName = "EnableClientLogFileMonitoring";
private const int _clientLogFilePollingIntervalMilliseconds = 30000; // 30 seconds

private static ClientLogFileWatcher _instance;
public static ClientLogFileWatcher Instance
{
get
{
if (_instance == null)
_instance = new ClientLogFileWatcher();

return _instance;
}
}

protected static FileSystemWatcher FileWatcher
{
get;
private set;
}

public delegate void ClientLogFileEventHandler(ClientLogFileWatcher sender, ClientLogFileEventArgs e);
public static event ClientLogFileEventHandler ClientLogFileChanged;

public DateTime LastDateTimeSeen
{
get;
protected set;
}

public long LastTimestampSeen
{
get;
protected set;
}

public long LastFileSizeSeen
{
get;
protected set;
}

protected System.Timers.Timer PollingTimer
{
get;
set;
}

protected Regex LocationChangedRegex
{
get;
set;
}

protected void Initialize()
{
if (!Settings.UserSettings.Keys.Contains(_clientLogFileLocationConfigName))
return;
string fullFilePath = Settings.UserSettings[_clientLogFileLocationConfigName];
if (string.IsNullOrWhiteSpace(fullFilePath))
return;

if (FileWatcher == null)
{
FileWatcher = new FileSystemWatcher();
FileWatcher.Path = Path.GetDirectoryName(fullFilePath);
FileWatcher.Filter = Path.GetFileName(fullFilePath);
FileWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size | NotifyFilters.LastAccess;

FileWatcher.Changed += OnFileChanged;
}

if (PollingTimer == null)
{
PollingTimer = new System.Timers.Timer();
PollingTimer.Elapsed += (s, e) => { ReadClientLogFile(); };
PollingTimer.Interval = _clientLogFilePollingIntervalMilliseconds;
}

// Regex to catch lines formatted like:
// 2019/06/21 19:16:37 245842781 aa1 [INFO Client 19844] : You have entered Sunspire Hideout.
LocationChangedRegex = new Regex(
@"(\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}) (\d+) [^ .]* \[.*\] : You have entered (.*).$",
RegexOptions.Compiled);
}

internal void Start()
{
if (!Settings.UserSettings.Keys.Contains(_enableClientLogFileMonitoringConfigName))
return;
var enabled = Convert.ToBoolean(Settings.UserSettings[_enableClientLogFileMonitoringConfigName]);
if (!enabled)
return;

if (FileWatcher == null)
Initialize();

FileWatcher.EnableRaisingEvents = true;

if (!PollingTimer.Enabled)
PollingTimer.Start();
}

internal void Stop()
{
if (FileWatcher != null)
FileWatcher.EnableRaisingEvents = false;

PollingTimer.Stop();
}

protected void ReadClientLogFile()
{
lock (Instance)
{
try
{
using (Stream stream = new FileStream(Settings.UserSettings[_clientLogFileLocationConfigName],
FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (var reader = new StreamReader(stream))
{
// Quit early if the log file is no longer than the last time we read it.
if (reader.BaseStream.Length <= Instance.LastFileSizeSeen)
{
Instance.LastFileSizeSeen = reader.BaseStream.Length;
return;
}

reader.BaseStream.Seek(Instance.LastFileSizeSeen, System.IO.SeekOrigin.Begin);

DateTime eventTime = Instance.LastDateTimeSeen;
long eventTimestamp = Instance.LastTimestampSeen;
string line, location;
while ((line = reader.ReadLine()) != null)
{
Match match = LocationChangedRegex.Match(line);
if (!match.Success)
continue;

eventTime = DateTime.ParseExact(match.Groups[1].Value, "yyyy/MM/dd HH:mm:ss",
System.Globalization.CultureInfo.InvariantCulture);
if (!long.TryParse(match.Groups[2].Value, out eventTimestamp))
{
Logger.Log(string.Format("Failed to parse event timestamp from string '{0}'.",
match.Groups[2].Value));
}
location = match.Groups[3].Value;

if ((DateTime.Now - eventTime).TotalSeconds > 600 ||
eventTime < Instance.LastDateTimeSeen)
{
continue;
}

ClientLogFileChanged?.Invoke(Instance,
new ClientLogFileEventArgs(eventTime, eventTimestamp, location));
}

Instance.LastDateTimeSeen = eventTime;
Instance.LastTimestampSeen = eventTimestamp;
Instance.LastFileSizeSeen = reader.BaseStream.Length;
}
}
catch (IOException ex)
{
Logger.Log(string.Format("Failed to open config log file: {0}", ex.ToString()));
}
}
}

protected static void OnFileChanged(object source, FileSystemEventArgs e)
{
Instance.ReadClientLogFile();
}
}
}
2 changes: 2 additions & 0 deletions Procurement/ViewModel/LoginWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,8 @@ public void Login(bool offline)

ApplicationState.SetDefaults();

ClientLogFileWatcher.Instance.Start();

if (!offline)
{
_statusController.DisplayMessage("\nDone!");
Expand Down
39 changes: 39 additions & 0 deletions Procurement/ViewModel/TabViewModel/StashViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ public class StashViewModel : ObservableBase
private bool currencyDistributionUsesCount;
private string filter;

private const string _enableTabRefreshOnLocationChangedConfigName = "EnableTabRefreshOnLocationChanged";

public string Filter
{
get { return filter; }
Expand Down Expand Up @@ -133,6 +135,32 @@ public SortedDictionary<string, int> GemDistribution
ScreenController.Instance.InvalidateRecipeScreen();
});

public static DateTime LastAutomaticRefresh { get; protected set; }
public void OnClientLogFileChanged(object sender, ClientLogFileEventArgs e)
{
// All actions currently taken when the log file changes relate to refreshing staash tabs. This checks
// first that we are logged in, and quits early if we are not.
if (!LoggedIn)
return;

lock (this)
{
if ((DateTime.Now - LastAutomaticRefresh).TotalSeconds <= 120)
return;
LastAutomaticRefresh = DateTime.Now;

Application.Current.Dispatcher.BeginInvoke(System.Windows.Threading.DispatcherPriority.Normal,
new Action(() =>
{
if (ScreenController.Instance.ButtonsVisible)
{
ScreenController.Instance.LoadRefreshViewUsed();
ScreenController.Instance.InvalidateRecipeScreen();
}
}));
}
}

public StashViewModel(StashView stashView)
{
this.stashView = stashView;
Expand All @@ -153,6 +181,17 @@ public StashViewModel(StashView stashView)
currencyDistributionUsesCount = true;
else
configuredOrbType = (OrbType)Enum.Parse(typeof(OrbType), currencyDistributionMetric);

if (Settings.UserSettings.Keys.Contains(_enableTabRefreshOnLocationChangedConfigName))
{
var enabled = false;
if (bool.TryParse(Settings.UserSettings[_enableTabRefreshOnLocationChangedConfigName], out enabled)
&& enabled)
{
ClientLogFileWatcher.ClientLogFileChanged -= OnClientLogFileChanged;
ClientLogFileWatcher.ClientLogFileChanged += OnClientLogFileChanged;
}
}
}

private void getAvailableItems()
Expand Down

0 comments on commit 3cc6fe0

Please sign in to comment.