Skip to content

Instantly share code, notes, and snippets.

@edongashi
Last active Jan 21, 2018
Embed
What would you like to do?
Tracks the content of a single file.
/// <summary>
/// Tracks the content of a single file.
/// </summary>
public sealed class FileWatcher : IDisposable, INotifyPropertyChanged
{
private class Watcher : IDisposable
{
private readonly List<FileWatcher> listeners;
private readonly FileSystemWatcher fileSystemWatcher;
private readonly string filePath;
private bool isLatestValue;
private string value;
public Watcher(FileWatcher initialListener)
{
filePath = initialListener.filePath;
listeners = new List<FileWatcher> { initialListener };
fileSystemWatcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(filePath),
Filter = Path.GetFileName(filePath),
EnableRaisingEvents = true
};
fileSystemWatcher.Created += (s, e) => Update();
fileSystemWatcher.Changed += (s, e) => Update();
fileSystemWatcher.Deleted += (s, e) => Update();
fileSystemWatcher.Renamed += (s, e) => Update();
fileSystemWatcher.Error += (s, e) => Update();
}
public string Value
{
get
{
lock (this)
{
if (!isLatestValue)
{
value = TryReadFile();
isLatestValue = true;
}
return value;
}
}
private set => this.value = value;
}
public int Count => listeners.Count;
public void AddListener(FileWatcher listener)
{
listeners.Add(listener);
}
public void RemoveListener(FileWatcher listener)
{
listeners.Remove(listener);
}
public void Dispose()
{
fileSystemWatcher.Dispose();
}
private void Update()
{
try
{
fileSystemWatcher.EnableRaisingEvents = false;
isLatestValue = false;
foreach (var listener in listeners)
{
listener.NotifyChanged();
}
}
finally
{
fileSystemWatcher.EnableRaisingEvents = true;
}
}
private string TryReadFile()
{
try
{
using (var stream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
{
using (var reader = new StreamReader(stream))
{
return reader.ReadToEnd();
}
}
}
catch
{
return "";
}
}
}
private static readonly Dictionary<string, Watcher> Watchers
= new Dictionary<string, Watcher>(StringComparer.OrdinalIgnoreCase);
private readonly string filePath;
private bool disposed;
public FileWatcher(string filePath)
{
filePath = Path.GetFullPath(filePath ?? throw new ArgumentNullException(nameof(filePath)));
this.filePath = filePath;
lock (Watchers)
{
if (Watchers.ContainsKey(filePath))
{
Watchers[filePath].AddListener(this);
}
else
{
Watchers[filePath] = new Watcher(this);
}
}
}
~FileWatcher()
{
Dispose(false);
}
public string Content
{
get
{
lock (Watchers)
{
if (disposed)
{
throw new ObjectDisposedException(nameof(FileWatcher));
}
return Watchers[filePath].Value;
}
}
}
public event EventHandler ContentChanged;
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyChanged()
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(nameof(Content)));
ContentChanged?.Invoke(this, EventArgs.Empty);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Dispose(bool disposing)
{
lock (Watchers)
{
if (disposed)
{
return;
}
disposed = true;
var watcher = Watchers[filePath];
watcher.RemoveListener(this);
if (watcher.Count == 0)
{
watcher.Dispose();
Watchers.Remove(filePath);
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment