Here is a solution that may be overkill for some users. I've created a new static class which has an event which is triggered only when the file finishes copying.
The user registers files which they would like to watch by calling FileAccessWatcher.RegisterWaitForFileAccess(filePath)
. If the file is not already being watched a new task is started which repeatedly checks the file to see if it can be opened. Each time it checks it also reads the file size. If the file size does not increase in a pre-defined time (5 minutes in my example) the loop is exited.
When the loop exits from the file being accessible or from the timeout the FileFinishedCopying
event is triggered.
public class FileAccessWatcher
{
// this list keeps track of files being watched
private static ConcurrentDictionary<string, FileAccessWatcher> watchedFiles = new ConcurrentDictionary<string, FileAccessWatcher>();
public static void RegisterWaitForFileAccess(string filePath)
{
// if the file is already being watched, don't do anything
if (watchedFiles.ContainsKey(filePath))
{
return;
}
// otherwise, start watching it
FileAccessWatcher accessWatcher = new FileAccessWatcher(filePath);
watchedFiles[filePath] = accessWatcher;
accessWatcher.StartWatching();
}
/// <summary>
/// Event triggered when the file is finished copying or when the file size has not increased in the last 5 minutes.
/// </summary>
public static event FileSystemEventHandler FileFinishedCopying;
private static readonly TimeSpan MaximumIdleTime = TimeSpan.FromMinutes(5);
private readonly FileInfo file;
private long lastFileSize = 0;
private DateTime timeOfLastFileSizeIncrease = DateTime.Now;
private FileAccessWatcher(string filePath)
{
this.file = new FileInfo(filePath);
}
private Task StartWatching()
{
return Task.Factory.StartNew(this.RunLoop);
}
private void RunLoop()
{
while (this.IsFileLocked())
{
long currentFileSize = this.GetFileSize();
if (currentFileSize > this.lastFileSize)
{
this.lastFileSize = currentFileSize;
this.timeOfLastFileSizeIncrease = DateTime.Now;
}
// if the file size has not increased for a pre-defined time limit, cancel
if (DateTime.Now - this.timeOfLastFileSizeIncrease > MaximumIdleTime)
{
break;
}
}
this.RemoveFromWatchedFiles();
this.RaiseFileFinishedCopyingEvent();
}
private void RemoveFromWatchedFiles()
{
FileAccessWatcher accessWatcher;
watchedFiles.TryRemove(this.file.FullName, out accessWatcher);
}
private void RaiseFileFinishedCopyingEvent()
{
FileFinishedCopying?.Invoke(this,
new FileSystemEventArgs(WatcherChangeTypes.Changed, this.file.FullName, this.file.Name));
}
private long GetFileSize()
{
return this.file.Length;
}
private bool IsFileLocked()
{
try
{
using (this.file.Open(FileMode.Open)) { }
}
catch (IOException e)
{
var errorCode = Marshal.GetHRForException(e) & ((1 << 16) - 1);
return errorCode == 32 || errorCode == 33;
}
return false;
}
}
Example usage:
// register the event
FileAccessWatcher.FileFinishedCopying += FileAccessWatcher_FileFinishedCopying;
// start monitoring the file (put this inside the OnChanged event handler of the FileSystemWatcher
FileAccessWatcher.RegisterWaitForFileAccess(fileSystemEventArgs.FullPath);
Handle the FileFinishedCopyingEvent:
private void FileAccessWatcher_FileFinishedCopying(object sender, FileSystemEventArgs e)
{
Console.WriteLine("File finished copying: " + e.FullPath);
}
File.Create(fileName)
. The answers are missing that point. It is not necessary to wait for closure. – Missie