mirror of
https://github.com/MCCTeam/Minecraft-Console-Client
synced 2025-10-14 21:22:49 +00:00
Move SessionFileMonitor into a generic FileMonitor class Use FileMonintor for both SessionCache and the Mailer bot Allows multiple MCC instances to share the same database files (Add files missing in the previous commit)
161 lines
6.2 KiB
C#
161 lines
6.2 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using System.Text;
|
|
using System.Threading;
|
|
|
|
namespace MinecraftClient
|
|
{
|
|
/// <summary>
|
|
/// Monitor file changes on disk
|
|
/// </summary>
|
|
public class FileMonitor : IDisposable
|
|
{
|
|
private FileSystemWatcher monitor = null;
|
|
private Thread polling = null;
|
|
|
|
/// <summary>
|
|
/// Create a new FileMonitor and start monitoring
|
|
/// </summary>
|
|
/// <param name="folder">Folder to monitor</param>
|
|
/// <param name="filename">Filename inside folder</param>
|
|
/// <param name="handler">Callback for file changes</param>
|
|
public FileMonitor(string folder, string filename, FileSystemEventHandler handler)
|
|
{
|
|
if (Settings.DebugMessages)
|
|
{
|
|
string callerClass = new System.Diagnostics.StackFrame(1).GetMethod().DeclaringType.Name;
|
|
ConsoleIO.WriteLineFormatted(String.Format("§8[{0}] Initializing FileSystemWatcher for file: {1}", callerClass, Path.Combine(folder, filename)));
|
|
}
|
|
|
|
try
|
|
{
|
|
monitor = new FileSystemWatcher();
|
|
monitor.Path = folder;
|
|
monitor.IncludeSubdirectories = false;
|
|
monitor.Filter = filename;
|
|
monitor.NotifyFilter = NotifyFilters.LastWrite;
|
|
monitor.Changed += handler;
|
|
monitor.EnableRaisingEvents = true;
|
|
}
|
|
catch
|
|
{
|
|
if (Settings.DebugMessages)
|
|
{
|
|
string callerClass = new System.Diagnostics.StackFrame(1).GetMethod().DeclaringType.Name;
|
|
ConsoleIO.WriteLineFormatted(String.Format("§8[{0}] Failed to initialize FileSystemWatcher, retrying using Polling", callerClass));
|
|
}
|
|
|
|
monitor = null;
|
|
polling = new Thread(() => PollingThread(folder, filename, handler));
|
|
polling.Name = String.Format("{0} Polling thread: {1}", this.GetType().Name, Path.Combine(folder, filename));
|
|
polling.Start();
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Stop monitoring and dispose the inner resources
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
if (monitor != null)
|
|
monitor.Dispose();
|
|
if (polling != null)
|
|
polling.Abort();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Fallback polling thread for use when operating system does not support FileSystemWatcher
|
|
/// </summary>
|
|
/// <param name="folder">Folder to monitor</param>
|
|
/// <param name="filename">File name to monitor</param>
|
|
/// <param name="handler">Callback when file changes</param>
|
|
private void PollingThread(string folder, string filename, FileSystemEventHandler handler)
|
|
{
|
|
string filePath = Path.Combine(folder, filename);
|
|
DateTime lastWrite = GetLastWrite(filePath);
|
|
while (true)
|
|
{
|
|
Thread.Sleep(5000);
|
|
DateTime lastWriteNew = GetLastWrite(filePath);
|
|
if (lastWriteNew != lastWrite)
|
|
{
|
|
lastWrite = lastWriteNew;
|
|
handler(this, new FileSystemEventArgs(WatcherChangeTypes.Changed, folder, filename));
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Get last write for a given file
|
|
/// </summary>
|
|
/// <param name="path">File path to get last write from</param>
|
|
/// <returns>Last write time, or DateTime.MinValue if the file does not exist</returns>
|
|
private DateTime GetLastWrite(string path)
|
|
{
|
|
FileInfo fileInfo = new FileInfo(path);
|
|
if (fileInfo.Exists)
|
|
{
|
|
return fileInfo.LastWriteTime;
|
|
}
|
|
else return DateTime.MinValue;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Opens a text file, reads all lines of the file, and then closes the file. Retry several times if the file is in use
|
|
/// </summary>
|
|
/// <param name="filePath">The file to open for reading</param>
|
|
/// <param name="maxTries">Maximum read attempts</param>
|
|
/// <param name="encoding">Encoding (default is UTF8)</param>
|
|
/// <exception cref="System.IO.IOException">Thrown when failing to read the file despite multiple retries</exception>
|
|
/// <returns>All lines</returns>
|
|
public static string[] ReadAllLinesWithRetries(string filePath, int maxTries = 3, Encoding encoding = null)
|
|
{
|
|
int attempt = 0;
|
|
if (encoding == null)
|
|
encoding = Encoding.UTF8;
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
return File.ReadAllLines(filePath, encoding);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
attempt++;
|
|
if (attempt < maxTries)
|
|
Thread.Sleep(new Random().Next(50, 100) * attempt); // Back-off like CSMA/CD
|
|
else throw;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Creates a new file, writes a collection of strings to the file, and then closes the file.
|
|
/// </summary>
|
|
/// <param name="filePath">The file to open for writing</param>
|
|
/// <param name="lines">The lines to write to the file</param>
|
|
/// <param name="maxTries">Maximum read attempts</param>
|
|
/// <param name="encoding">Encoding (default is UTF8)</param>
|
|
public static void WriteAllLinesWithRetries(string filePath, IEnumerable<string> lines, int maxTries = 3, Encoding encoding = null)
|
|
{
|
|
int attempt = 0;
|
|
if (encoding == null)
|
|
encoding = Encoding.UTF8;
|
|
while (true)
|
|
{
|
|
try
|
|
{
|
|
File.WriteAllLines(filePath, lines, encoding);
|
|
}
|
|
catch (IOException)
|
|
{
|
|
attempt++;
|
|
if (attempt < maxTries)
|
|
Thread.Sleep(new Random().Next(50, 100) * attempt); // Back-off like CSMA/CD
|
|
else throw;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|