Skip to content

Instantly share code, notes, and snippets.

@walterlv
Last active March 19, 2019 01:53
Show Gist options
  • Save walterlv/cffec6dd951780ea946feb2ea96f302a to your computer and use it in GitHub Desktop.
Save walterlv/cffec6dd951780ea946feb2ea96f302a to your computer and use it in GitHub Desktop.
FileWatcher that helps you to watch a single file change even if the file or it's owner folders does not exists.
using System;
using System.IO;
namespace Walterlv.IO
{
public sealed class FileWatcher
{
/// <summary>监视的文件。</summary>
private readonly FileInfo _file;
/// <summary>获取当前监视的文件夹监视器。可能会因为文件(夹)不存在而改变。</summary>
private FileSystemWatcher _watcher;
public FileWatcher(string fileName)
{
var file = fileName ?? throw new ArgumentNullException(nameof(fileName));
_file = new FileInfo(file);
}
public FileWatcher(FileInfo file)
{
_file = file ?? throw new ArgumentNullException(nameof(file));
}
public event EventHandler Changed;
private void OnChanged()
{
Changed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// 监视文件的改变。
/// </summary>
/// <remarks>
/// 此方法可以被重复调用,不会引发异常或导致重复监视。
/// </remarks>
public void Watch()
{
Stop();
var (directory, file) = FindWatchableLevel();
if (File.Exists(_file.FullName))
{
// 如果文件存在,说明这是最终的文件。
// 注意使用 File.Exists 判断已存在的同名文件夹时会返回 false。
_watcher = new FileSystemWatcher(directory, file)
{
EnableRaisingEvents = true,
NotifyFilter = NotifyFilters.LastWrite,
};
_watcher.Changed += FinalFile_Changed;
_watcher.Deleted += FileOrDirectory_CreatedOrDeleted;
}
else
{
// 注意这里的 file 可能是文件也可能是文件夹。
_watcher = new FileSystemWatcher(directory, file)
{
EnableRaisingEvents = true,
};
_watcher.Created += FileOrDirectory_CreatedOrDeleted;
_watcher.Renamed += FileOrDirectory_CreatedOrDeleted;
_watcher.Deleted += FileOrDirectory_CreatedOrDeleted;
}
}
/// <summary>
/// 停止监视文件的改变。
/// </summary>
/// <remarks>
/// 此方法可以被重复调用。
/// </remarks>
public void Stop()
{
// 文件 / 文件夹已经创建,所以之前的监视不需要了。
// 文件 / 文件夹被删除了,所以之前的监视没法儿用了。
// Dispose 之后,这个对象就没用了,事件也不会再引发,所以不需要注销事件。
_watcher?.Dispose();
_watcher = null;
}
private void FileOrDirectory_CreatedOrDeleted(object sender, FileSystemEventArgs e)
{
Watch();
}
private void FinalFile_Changed(object sender, FileSystemEventArgs e)
{
OnChanged();
}
/// <summary>
/// 从 <see cref="_file"/> 开始寻找第一层存在的文件夹,返回里面的文件。
/// </summary>
private (string directory, string file) FindWatchableLevel()
{
var path = _file.FullName;
// 如果文件存在,就返回文件所在的文件夹和文件本身。
if (File.Exists(path))
{
return (Path.GetDirectoryName(path), Path.GetFileName(path));
}
// 如果文件不存在,但文件夹存在,也是返回文件夹和文件本身。
// 这一点在下面的第一层循环中体现。
// 对于每一层循环。
while (true)
{
var directory = Path.GetDirectoryName(path);
var file = Path.GetFileName(path);
// 检查文件夹是否存在,只要文件夹存在,那么就可以返回。
if (Directory.Exists(directory))
{
return (directory, file);
}
// 如果连文件夹都不存在,那么就需要查找上一层文件夹。
path = directory;
}
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment