I am using a FileSystemWatcher in my code to track any change/rename/addition of file under the monitored directory. Now I need a notification if the monitored directory itself gets deleted.
Any suggestions on how to achieve this?
I was attempting to add a second watcher on the parent directory ( C:\temp\subfolder1 in the sample below) and filter the events to the fullpath of the monitored directory ( C:\temp\subfolder1\subfolder2).
But this won't work if the deletion is done a directory level higher and I do not want to monitor the whole file system. In the sample below it should fire on deleting C:\temp as well, not only on deleting C:\temp\subfolder1.
class Program
{
static void Main(string[] args)
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = #"C:\temp\subfolder1\subfolder2";
watcher.EnableRaisingEvents = true;
watcher.Changed += OnChanged;
watcher.Created += OnChanged;
watcher.Deleted += OnChanged;
watcher.Renamed += OnChanged;
Console.ReadLine();
}
private static void OnChanged(object sender, FileSystemEventArgs e)
{
Console.WriteLine(e.FullPath);
}
}
You can subscribe to the FileSystemWatcher.Error Event. This will fire when the parent directory gets deleted. Then you can make the appropriate checks to see if this was caused by the folder deletion.
#crankedrelic Thanks for contributing!
In order to get both deletion cases (permanently and move to recycle bin) I have to use this code:
private static void OnError(object sender, ErrorEventArgs e)
{
Exception ex = e.GetException();
if (ex is Win32Exception && (((Win32Exception)ex).NativeErrorCode == 5))
{
Console.WriteLine($"Directory deleted permanently: { watcher.Path }");
}
}
private static void OnChanged(object sender, FileSystemEventArgs e)
{
if (!Directory.Exists(watcher.Path))
{
Console.WriteLine($"Directory deleted (recycle bin): { watcher.Path }");
}
}
Related
FileSystemWatcher works if I change the file with notepad.exe, but not if I change the file with VisualStudio. Why?
See also: Powershell File Watcher Not Picking Up File Changes Made in Visual Studio
static void FileWatcher()
{
FileSystemWatcher watcher = new FileSystemWatcher
{
Path = Path.GetDirectoryName(#"D:\Test\"),
NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.Size,
Filter = "file.txt",
EnableRaisingEvents = true
};
watcher.Changed += OnFileChanged;
}
static void OnFileChanged(object sender, FileSystemEventArgs e)
{
Console.WriteLine("{0} Watcher: {1} {2}", DateTime.Now, e.ChangeType, e.FullPath);
}
PS. watcher.Renamed works. Thank you mjwills.
By adding the following lines of code you should be able to capture all events.
watcher.Deleted += OnFileChanged;
watcher.Created += OnFileChanged;
I have a hidden share:
\\computername\Logs$
And I need to monitor file changes in that share.
I've decided to use FileSystemWatcher Class, but it doesn't raise any events. And it doesn't show any errors to me.
static void Main(string[] args)
{
FileWatcher fw = new FileWatcher(#"\\computername\Logs$", "*.*");
fw.Start();
}
class FileWatcher(string filePath, string mask)
{
FileSystemWatcher watcher;
watcher.Path = filePath;
watcher.Filter = mask;
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Changed += Watcher_Changed;
watcher.Error += OnError;
public void Start()
{
watcher.InternalBufferSize = 64 * 1024;
watcher.EnableRaisingEvents = true;
Console.WriteLine("Watcher Started");
while (!Console.KeyAvailable)
{
Thread.Sleep(1000);
}
}
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
Console.WriteLine("File Changed");
}
private void OnError(object sender, ErrorEventArgs e)
{
Console.WriteLine("Error");
}
}
Does FileSystemWatcher work properly with hidden shares?
This code will not compile ,maybe you had errors while copy the code
and where the event handler Watcher_Changed, maybe you have mistake and put Watcher_Created instead
I have this code, which should keep richTextBox2 updated at all times with usedPath's contents, but it doesn't.
private void watch()
{
var usedPath = Path.Combine(Directory.GetCurrentDirectory(), "usedwords.txt");
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = usedPath;
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Filter = "*.txt*";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
string usedPath = Path.Combine(Directory.GetCurrentDirectory(), "usedwords.txt");
richTextBox2.LoadFile(usedPath, RichTextBoxStreamType.PlainText);
}
Can someone help me figure out what I have wrong?
Problem 1: Your watcher.Path = path of a single file, which will cause error.
Solution: Look at this: Use FileSystemWatcher on a single file in C#
watcher.Path = Path.GetDirectoryName(filePath1);
watcher.Filter = Path.GetFileName(filePath1);
Problem 2: Accessing richTextBox2 in OnChanged() will cause cross-thread error
Solution: Use this:
private void OnChanged(object source, FileSystemEventArgs e)
{
Invoke((MethodInvoker)delegate
{
string usedPath = Path.Combine(Directory.GetCurrentDirectory(), "usedwords.txt");
richTextBox2.LoadFile(usedPath, RichTextBoxStreamType.PlainText);
});
}
Problem 3: There may be error when trying to LoadFile while some other programs are writing to it.
(Possible) Solution: Put a Thread.Sleep(10) in before trying to LoadFile in OnChanged
private void OnChanged(object source, FileSystemEventArgs e)
{
Thread.Sleep(10);
Invoke((MethodInvoker)delegate
{
richTextBox1.LoadFile(usedPath, RichTextBoxStreamType.PlainText);
});
}
My complete code:
public partial class Form1 : Form
{
string usedPath = #"C:\Users\xxx\Desktop\usedwords.txt";
public Form1()
{
InitializeComponent();
watch();
}
private void watch()
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = Path.GetDirectoryName(usedPath);
watcher.Filter = Path.GetFileName(usedPath);
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
Thread.Sleep(10);
Invoke((MethodInvoker)delegate
{
richTextBox1.LoadFile(usedPath, RichTextBoxStreamType.PlainText);
});
}
}
I am using FileSystemWatcher to monitor a folder that will be used to do some file renaming.
The only thing that will be copied will be folders containing files. There will not be single files put into the monitored folder. This is the code for setting up the FileSystemWatcher
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName;
watcher.IncludeSubdirectories = true;
watcher.Filter = "*.*";
watcher.Created += new FileSystemEventHandler(watcher_Created);
watcher.Changed += new FileSystemEventHandler(watcher_Changed);
watcher.Renamed += new RenamedEventHandler(watcher_Renamed);
watcher.EnableRaisingEvents = true;
There doesn't seem to be any issues with this setup..
The folders being copied can be between 50-200mb big. Is there a way to check/make sure that all the files have completed copying before starting the renaming process.
I tried this thinking that i would get an IOException if the copying was still happening when the GetFiles() was called.
bool finishedCopying = false;
while (!finishedCopying)
{
try
{
List<FileInfo> fileList = directoryInfo.GetFiles().ToList();
AlbumSearch newAlbum = new AlbumSearch(directoryInfo);
return newAlbum;
}
catch (IOException)
{
finishedCopying = false;
}
}
If anymore information is required, just ask an i can provide.
Ta.
I gave this a go using a timer. It may not be the prettiest solution out there but at first testing it seems to be working so far. Essentially what this does is when a folder is copied to the monitored folder it will add the folder path to the AlbumList. The files in that folder will trigger the Created event. This waits for the file to finish copying. Once finished it starts a timer. If a new Created event gets triggered the timer will reset itself.
When the timer.elapsed event is triggered it assumes (and I know assumption is the mother of all f*&k ups) that there are no more files to be copied and can start to process the fully copied folder..
System.Timers.Timer eventTimer = new System.Timers.Timer();
List<string> AlbumList = new List<string>();
private void watcher_Created(object sender, FileSystemEventArgs e)
{
if (Directory.Exists(e.FullPath))
{
AlbumList.Add(e.FullPath);
}
if (File.Exists(e.FullPath))
{
eventTimer.Stop();
FileInfo newTrack = new FileInfo(e.FullPath);
while (IsFileLocked(newTrack))
{
// File is locked. Do Nothing..
}
eventTimer.Start();
}
}
private void eventTimer_Elapsed(object sender, ElapsedEventArgs e)
{
List<string> ItemToRemove = new List<string>();
foreach (var item in AlbumList)
{
DirectoryInfo di = new DirectoryInfo(item);
AlbumSearch newAlbum = new AlbumSearch(di);
if (DoSomethingMethod(newAlbum))
{
ItemToRemove.Add(item);
}
else
{
// why did it fail
}
}
foreach (var path in ItemToRemove)
{
AlbumList.Remove(path);
}
}
private bool DoSomethingMethod(AlbumSearch as)
{
// Do stuff here
return true;
}
This is a small demo app that check files at the beginning, and then uses two hashsets to track copied files. This will only work if the source directory is known. There is no way to know if a file was created from a file copy or from a direct creation, so you can only compare two known directories with Directory.GetFiles. And, as already said in comments, you will still have to check if during the copy process, other files were added / removed / renamed in the old directory
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApplication1
{
class Program
{
static HashSet<string> oldDirFiles = new HashSet<string>();
static HashSet<string> newDirFiles = new HashSet<string>();
static string oldDir = "C:\\New Folder";
static string newDir = "C:\\New Folder 2";
static System.Threading.ManualResetEvent resetEvent = new System.Threading.ManualResetEvent(false);
static void Main(string[] args)
{
System.IO.FileSystemWatcher watcher = new System.IO.FileSystemWatcher();
watcher.Path = newDir;
watcher.NotifyFilter = NotifyFilters.DirectoryName | NotifyFilters.FileName;
watcher.IncludeSubdirectories = true;
watcher.Filter = "*.*";
watcher.Created += watcher_Created;
watcher.Changed += watcher_Changed;
watcher.Renamed += watcher_Renamed;
watcher.EnableRaisingEvents = true;
//get all files in old directory
var oldFiles = Directory.GetFiles(oldDir, "*.*", SearchOption.AllDirectories);
foreach (var file in oldFiles)
oldDirFiles.Add(file);
resetEvent.WaitOne();
//now launch the directory copy
//then you have to check if in the meaning time, new files were added or renamed
//that could be done also with a watcher in the old directory
}
static void watcher_Renamed(object sender, RenamedEventArgs e)
{
throw new NotImplementedException();
}
static void watcher_Changed(object sender, FileSystemEventArgs e)
{
throw new NotImplementedException();
}
static void watcher_Created(object sender, FileSystemEventArgs e)
{
//check if the copied file was in the old directory before starting
if (oldDirFiles.Contains(e.FullPath.Replace(newDir, oldDir)))
{
newDirFiles.Add(e.FullPath);
//if all the files have been copied, the file count will be the same in the two hashsets
//the resetevent.Set() signal the waiting thread and the program can proceed
if (newDirFiles.Count == oldDirFiles.Count)
resetEvent.Set();
}
}
}
}
Using FileSystemWatcher, is there a way to dispose of instances of event listeners after an event has fired?
My program basically listens for the creation of a batch.complete.xml file in newly created folders. Once the program detects that the file has been created there is no need to continue listening in this folder.
My program looks like this:
public static void watchxmlfile(batchfolderpath){
var deliverycompletewatcher = new FileSystemWatcher();
deliverycompletewatcher.Path = batchfolderpath;
deliverycompletewatcher.Filter = "*.xml";
deliverycompletewatcher.Created += new FileSystemEventHandler(OnChanged);
deliverycompletewatcher.EnableRaisingEvents = true;
}
private static void OnChanged(object sender, FileSystemEventArgs e)
{
BuildingestionXml(string.Format(#"{0}\{1}",e.FullPath,e.Name));
Console.WriteLine(#"Second: Success sending{0}\{1}", e.FullPath, e.Name);
}
So when the above event is fired I no longer need to watch for events in "batchfolderpath" unless watchxmlfile() is explicitly called which will have a new path.
I am trying to prevent memory leaks from too many instances of listeners for the above event.
In you EventHandler you can just un-assign the event, But you will have to declare the FileSystemWatcher as a variable.
Example
private static FileSystemWatcher deliverycompletewatcher;
public static void watchxmlfile(string batchfolderpath)
{
deliverycompletewatcher = new FileSystemWatcher();
deliverycompletewatcher.Path = batchfolderpath;
deliverycompletewatcher.Filter = "*.xml";
deliverycompletewatcher.Created += OnChanged;
deliverycompletewatcher.EnableRaisingEvents = true;
}
private static void OnChanged(object sender, FileSystemEventArgs e)
{
deliverycompletewatcher.EnableRaisingEvents = false;
deliverycompletewatcher.Created -= OnChanged;
// Do some cool stuff
}
You don't need to assign a variable, sender is the FileSystemWatcher:
private static void OnChanged(object sender, FileSystemEventArgs e)
{
BuildingestionXml(string.Format(#"{0}\{1}",e.FullPath,e.Name));
Console.WriteLine(#"Second: Success sending{0}\{1}", e.FullPath, e.Name);
((FileSystemWatcher)sender).Dispose();
}