Reputation: 4468
I am using FileSystemWatcher
class in C#
to track the changes on a file whenever the user saves the file after modifying it.
FileSystemWatcher watcher = new FileSystemWatcher()
{
Path = DIR_NAME,
NotifyFilter = NotifyFilters.LastWrite |
NotifyFilters.CreationTime |
NotifyFilters.LastAccess,
Filter = "*.pdf",
IncludeSubdirectories = true,
EnableRaisingEvents = true
};
watcher.Changed += OnChanged;
However, the file that I want to track is getting created programmatically, as follows:
FileStream stream = FileUtil.CreateNewFile(filePath); // Creates a file
file.WriteFileToStream(stream); // Writes into the file
Ideally, my code is supposed to run as follows:
OnChanged
, i.e. I want my OnChanged
handler code to execute only when a real user modifies it and saves it. However, it's getting triggered whenever the file is getting written into programmatically, i.e. on the following line:
file.WriteFileToStream(stream);
Which is, technically, correct behavior, as it's tracking the change in the file. However, my business case doesn't allow the OnChanged
handler code to be executed when the file is initially created and written into.
My question is, is there a workaround to skip the OnChanged
call when the file is created and written into first time programmatically?
Note:
FileSystemWatcher
is initialized when my application starts. So I cannot register it after the file is created. watcher.EnableRaisingEvents = false;
CreateFile();
watcher.EnableRaisingEvents = true;
Upvotes: 2
Views: 2087
Reputation: 81523
This is a sticky situation and there is not much you can do about it apart from checking if the file is locked (assuming it has a read or write lock):
public bool IsFileLocked(string fileName)
{
try
{
using (var stream = File.Open(fileName, FileMode.Open, FileAccess.Read, FileShare.None))
{
return false;
}
}
catch (IOException)
{
//the file is unavailable
return true;
}
}
The premise is when you get an update, you check if the file is locked, if it isn't then you can loosely assume it's been closed.
Some things to consider though:
FileAccess.Read
, so this doesn't fail on read only files.FileSystemWatcher
for various reasons, and it can't always be relied on in certain situation; read the documentation for the reasons why events can be dropped and how to fix them. In these cases, you could possible get away with a long poll and some logic to pick up any stragglers (depending on your situation).Upvotes: 0
Reputation: 4468
Approach One:
Approach Two:
When a file is created, use OnCreated()
event handler to add the file name to a filesToIgnore
list.
In the OnChanged
event handler, check if the filesToIgnore
list contains the file name. If it does, remove it from the list (to process it next time) and return from the handler.
private List<string> filesToIgnore = new List<string>();
private void OnCreated(object source, FileSystemEventArgs file)
{
filesToIgnore.Add(file.Name);
}
private void OnChanged(object source, FileSystemEventArgs file)
{
if(filesToIgnore.Contains(file.Name))
{
filesToIgnore.Remove(file.Name);
return;
}
// Code to execute when user saves the file
}
This approach assumes that OnCreated()
will be always triggered before OnChanged().
Upvotes: 1
Reputation: 43728
The extension method bellow allows handling events from user's actions only:
public static void OnChangedByUser(this FileSystemWatcher fsw,
FileSystemEventHandler handler)
{
const int TOLERANCE_MSEC = 100;
object locker = new object();
string fileName = null;
Stopwatch stopwatch = new Stopwatch();
fsw.Created += OnFileCreated;
fsw.Changed += OnFileChanged;
fsw.Disposed += OnDisposed;
void OnFileCreated(object sender, FileSystemEventArgs e)
{
lock (locker)
{
fileName = e.Name;
stopwatch.Restart();
}
}
void OnFileChanged(object sender, FileSystemEventArgs e)
{
lock (locker)
{
if (e.Name == fileName && stopwatch.ElapsedMilliseconds < TOLERANCE_MSEC)
{
return; // Ignore this event
}
}
handler.Invoke(sender, e);
}
void OnDisposed(object sender, EventArgs e)
{
fsw.Created -= OnFileCreated;
fsw.Changed -= OnFileChanged;
fsw.Disposed -= OnDisposed;
}
}
Usage example:
var fsw = new FileSystemWatcher(DIR_NAME);
fsw.OnChangedByUser(File_ChangedByUser);
fsw.EnableRaisingEvents = true;
private static void File_ChangedByUser(object sender, FileSystemEventArgs e)
{
// Handle the event
}
Upvotes: 0
Reputation: 4468
I added the following code in OnChanged
handler, and it seems to be working as expected.
private void OnChanged(object source, FileSystemEventArgs file)
{
if (file.ChangeType == WatcherChangeTypes.Created)
return;
FileInfo fileInfo = new FileInfo(file.FullPath);
if (fileInfo.CreationTime == fileInfo.LastWriteTime)
return;
// Handle the Changed event
...
}
However, I am not sure if I am missing something that will cause this to break. Any thoughts?
Upvotes: 0