using System; using System.Collections.Generic; using System.IO; using System.Text; using System.Threading; namespace MinecraftClient { /// /// Monitor file changes on disk /// public class FileMonitor : IDisposable { private FileSystemWatcher monitor = null; private Thread polling = null; /// /// Create a new FileMonitor and start monitoring /// /// Folder to monitor /// Filename inside folder /// Callback for file changes 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(); } } /// /// Stop monitoring and dispose the inner resources /// public void Dispose() { if (monitor != null) monitor.Dispose(); if (polling != null) polling.Abort(); } /// /// Fallback polling thread for use when operating system does not support FileSystemWatcher /// /// Folder to monitor /// File name to monitor /// Callback when file changes 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)); } } } /// /// Get last write for a given file /// /// File path to get last write from /// Last write time, or DateTime.MinValue if the file does not exist private DateTime GetLastWrite(string path) { FileInfo fileInfo = new FileInfo(path); if (fileInfo.Exists) { return fileInfo.LastWriteTime; } else return DateTime.MinValue; } /// /// Opens a text file, reads all lines of the file, and then closes the file. Retry several times if the file is in use /// /// The file to open for reading /// Maximum read attempts /// Encoding (default is UTF8) /// Thrown when failing to read the file despite multiple retries /// All lines 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; } } } /// /// Creates a new file, writes a collection of strings to the file, and then closes the file. /// /// The file to open for writing /// The lines to write to the file /// Maximum read attempts /// Encoding (default is UTF8) public static void WriteAllLinesWithRetries(string filePath, IEnumerable lines, int maxTries = 3, Encoding encoding = null) { int attempt = 0; if (encoding == null) encoding = Encoding.UTF8; while (true) { try { File.WriteAllLines(filePath, lines, encoding); return; } catch (IOException) { attempt++; if (attempt < maxTries) Thread.Sleep(new Random().Next(50, 100) * attempt); // Back-off like CSMA/CD else throw; } } } } }