How to end filewatcher from another method? - c#

Below is my code for a File watcher class I wrote:
class FileWatcher
{
#region Method that begins watching
public static void watch()
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = ConfigurationManager.AppSettings["OpticusFileLoc"];
watcher.Filter = ConfigurationManager.AppSettings["OpticusFileName"];
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Changed += new FileSystemEventHandler(OnChanged);
Console.Write("\nWatcher started. Press any key to end.\n");
watcher.EnableRaisingEvents = true;
}
#endregion
#region Trigger function on change
public static void OnChanged(object source, FileSystemEventArgs e)
{
Console.WriteLine("File has been changed.\n");
//watcher.EnableRaisingEvents = false ;
//Program.Main();
}
#endregion
}
How can I, from the OnChanged method, set the watcher.EnableRaisingEvents flag to false?
I guess I could do it by moving the declaration of the FileSystemWatcher object outside the method it is in, but I'm wondering if there's another way to do it.

I guess I could do it by moving the
declaration of the FileSystemWatcher
object outside the method it is in,
but I'm wondering if there's another
way to do it.
That's exactly how you should do it (have watcher just be a member field of your FileWatcher class).
That said, you also could do it like this:
public static void OnChanged(object source, FileSystemEventArgs e)
{
Console.WriteLine("File has been changed.");
var watcher = source as FileSystemWatcher;
if (watcher != null)
{
watcher.EnableRaisingEvents = false;
}
else
{
// Hmm... some other object called this method.
// Do you really want that to be allowed?
}
}
Notice the comment I added in the code above. Generally it's a bad call to make methods such as this public, as this makes it possible for any arbitrary code to call OnChanged even when doing so makes no sense. (You want for this method to capture the event of a file being changed; if it's actually callable from anywhere, then how do you know if the file was in fact changed or if this is just some random call from elsewhere?)

source is probably the FileSystemWatcher in question, so try this:
((FileSystemWatcher)source).EnableChangingEvents = false;

The object source parameter should be your FileSystemWatcher object, so you could just use that to shut it down.
public static void OnChanged(object source, FileSystemEventArgs e)
{
Console.WriteLine("File has been changed.\n");
(source as FileSystemWatcher).EnableRaisingEvents = false;
}

Is this the kind of thing you want to do?
public static void OnChanged(object source, FileSystemEventArgs e)
{
FileSystemWatcher watcher = (FileSystemWatcher)source;
watcher.EnableRaisingEvents = false ;
}

Related

Why does following Directory.Delete throw IOException The directory is not empty

I am creating a directory and setting up a FileSystemWatcher on it.
Then I create a file. All this is done in the form constructor. In a button event handler I delete the directory created above. Sometimes it throws a IOException: The directory is not empty. After this I cannot even access the child directory in explorer. I keep getting Access is Denied Errors. This directory gets deleted after my process exits.
AFAIK FileSystemWatcher should not a lock a directory.
string alphaBeta = #"Alpha\Beta";
public Form1()
{
InitializeComponent();
Directory.CreateDirectory(alphaBeta);
FileSystemWatcher watcher = new FileSystemWatcher()
{
Path = alphaBeta,
Filter = "*.dat",
NotifyFilter = NotifyFilters.FileName
};
watcher.EnableRaisingEvents = true;
File.WriteAllText(alphaBeta + #"\Gamma.dat", "Delta");
}
private void btnDelete_Click(object sender, EventArgs e)
{
Directory.Delete("Alpha", true);//Recursively Delete
}
How do I properly delete the directory without getting the directory stuck?
UPDATE:
Above is minimal reproducible example
My actual scenario involves a addin loaded in explorer.exe that monitors a config directory for changes. It hooks into create and rename events of FSW.
The delete code runs in the uninstaller. Uninstaller is not supposed to kill explorer. We thought FileSystemWatcher will not lock the Folder and will silently stop monitoring changes once the directory is deleted. But this doesn't seem to be the case.
The problem is caused because the FSW keeps a handle to the directory open as long as events are enabled. The source code shows that disabling events also closes the handle. To delete the directory, the FSW must be disabled.
The second problem though is that FileSystemWatcher watcher is defined as a variable inside the Form1() constructor, which means it's orphaned and immediatelly available for garbage collection before the form is even displayed. The garbage collector runs infrequently though, which means there's no guarantee it will run before Delete is clicked. Since the watcher isn't stored anywhere, iIt's no longer possible to disable it.
At the very least, the FSW needs to be stored in a field and events disabled before deletion. We should also ensure the watcher is disposed when the form itself gets disposed :
public class Form1
{
FileSystemWatcher _watcher;
public Form1()
{
...
_watcher=CreateDormantWatcher(path,pattern);
_watcher.EnableRaisingEvents=true ;
}
private void btnDelete_Click(object sender, EventArgs e)
{
_watcher.EnableRaisingEvents =false;
Directory.Delete("Alpha", true);//Recursively Delete
}
protected override void Dispose (bool disposing)
{
base.Dispose(disposing);
if (disposing)
{
_watcher.Dispose();
}
_watcher=watcher;
}
FileSystemWatcher CreateDormantWatcher(string path,string pattern)
{
//Don't store to the field until the FSW is
//already configured
var watcher=new FileSystemWatcher()
{
Path = path,
Filter = "pattern,
NotifyFilter = NotifyFilters.FileName
};
watcher.Changed += OnChanged;
watcher.Created += OnCreated;
watcher.Deleted += OnChanged;
watcher.Renamed += OnRenamed;
return watcher;
}
Easy fix: Add it as a component
Perhaps a better idea though, would be to add the FileSystemWatcher on the form as a component. FileSystemWatcher inherits from Component, which means that placing it on the form add its creation and configuration code in InitializeComponents(). It will also get disposed when all other components get disposed.
If we do that, we'd just have to toggle EnableRaisingEvents when appropriate.
Assuming the path and pattern are set as properties, and the component's name is the imaginative FileSystemWatcher1 this brings the code down to :
public Form1()
{
InitializeComponent();
Directory.CreateDirectory(FileSystemWatcher1.Path);
FileSystemWatcher1.EnableRaisingEvents = true;
File.WriteAllText(alphaBeta + #"\Gamma.dat", "Delta");
}
private void btnDelete_Click(object sender, EventArgs e)
{
FileSystemWatcher1.EnableRaisingEvents = false;
Directory.Delete("Alpha", true);//Recursively Delete
}
This appears to be an issue with the FileSystemWatcher. If the code for this is commented out then the Exception does not occur.
It would appear that the FileSystemWatcher is not necessarily disposed before the Directory.Delete method is called.
It would be appropriate to handle the FileSystemWatcher in a way that causes it to be disposed before deleting resources that it may have depended upon, but in your example code it's easily achieved by simply adding watcher.Dispose();:
string alphaBeta = #"Alpha\Beta";
public Form1()
{
InitializeComponent();
Directory.CreateDirectory(alphaBeta);
FileSystemWatcher watcher = new FileSystemWatcher()
{
Path = alphaBeta,
Filter = "*.dat",
NotifyFilter = NotifyFilters.FileName
};
watcher.EnableRaisingEvents = true;
watcher.Dispose();
File.WriteAllText(alphaBeta + #"\Gamma.dat", "Delta");
}
private void button1_Click(object sender, EventArgs e)
{
Directory.Delete("Alpha", true);//Recursively Delete
}

FileSystemWatcher - Reading last line of file after update?

I'm trying to write a simple console application that waits for a change to a file then reads just the last line of that file. The file watcher works and the change event fires. But I'm struggling to work out how read from the file.
static void Main(string[] args)
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Path = "E:\\myFilePath";
watcher.Filter = "";
watcher.EnableRaisingEvents = true;
watcher.Changed += new FileSystemEventHandler(watcher_Changed);
Console.ReadKey();
}
static void watcher_Changed(object sender, FileSystemEventArgs e)
{
Console.WriteLine(File.ReadLines(e.FullPath).Last());
}
when testing (editing the file in notepad) it will work once, then after a second edit I get the error...
System.IO.IOException: 'The process cannot access the file '' because
it is being used by another process.'
I still ran into problems using lines.GetEnumerator().Dispose();
You can add a pause before opening the file, just chose your poison for doing so.
static void watcher_Changed(object sender, FileSystemEventArgs e)
{
for (int x = 0; x <= 500000; x++)
{
int t = x;
}
Console.WriteLine(File.ReadLines(e.FullPath).Last());
}
I tried using SreamReader too but still ran into the same problem.
// Does not work
using (StreamReader r = new StreamReader(e.FullPath))
{
while (r.EndOfStream == false)
{
m = r.ReadLine();
}
r.Close();
}
Console.WriteLine("{0}\n", m);
The problem is due to an already open handle to the file. File.ReadLines uses an iterator internally which is Disposable. Use this to ensure Disposal during the lifetime of your event handler.
static void watcher_Changed(object sender, FileSystemEventArgs e)
{
var lines = File.ReadLines(e.FullPath);
Console.WriteLine(lines.Last());
lines.GetEnumerator().Dispose();
}
This should guarantee disposal before the end of event handler.
Edit: In the case that a different process is holding a write lock to the file, use Thread.Sleep(ms) with a safe time as inspecting and releasing file handles is not feasible in C#.

Lambda Expressions with the File System Watcher

I can't figure out for the life of me where the two arguments come from in the following code. I've compiled it in Visual Studio, and it works, but when you use the lambda expressions to add the anonymous methods to the delegates for the FileSystemWatcher, how do the methods ever receive those two arguments? Where do they come from? Does the FileSystemWatcher return an array with the two arguments when the .Changed or .OnChanged events occur? If so, I haven't been able to find documentation that explains that. Here is the code:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MyDirectoryWatcher
{
class Program
{
static void Main(string[] args)
{
Console.WriteLine("***** The File Watcher App *****\n");
// Establish the path to the directory to watch.
FileSystemWatcher watcher = new FileSystemWatcher();
try
{
watcher.Path = #"C:\MyFolder";
}
catch (ArgumentException ex)
{
Console.WriteLine(ex.Message);
Console.WriteLine("Press Enter to continue...");
Console.ReadLine();
return;
}
// Set up the things to be on the lookout for.
watcher.NotifyFilter = NotifyFilters.LastAccess
| NotifyFilters.LastWrite
| NotifyFilters.FileName
| NotifyFilters.DirectoryName;
// Only watch text files.
watcher.Filter = "*.txt";
// Specify what is done when a file is changed, created, or deleted.
watcher.Changed += (s, e) =>
{
Console.WriteLine("File: {0} {1}!", e.FullPath, e.ChangeType);
};
watcher.Created += (s, e) =>
{
Console.WriteLine("File: {0} {1}!", e.FullPath, e.ChangeType);
};
watcher.Deleted += (s, e) =>
{
Console.WriteLine("File: {0} {1}!", e.FullPath, e.ChangeType);
};
watcher.Renamed += (s, e) =>
{
// Specify what is done when a file is renamed.
Console.WriteLine("File: {0} renamed to {1}", e.OldFullPath, e.FullPath);
};
// Begin watching the directory.
watcher.EnableRaisingEvents = true;
// Wait for the user to quit the program.
Console.WriteLine(#"Press 'q' to quit app.");
while (Console.Read() != 'q') ;
}
}
}
Just to pick one of the events...
watcher.Changed += OnChanged;
private void OnChanged(object sender, FileSystemEventArgs e){
// Handle event
}
You're actually adding a delegate to the Changed event's invocation list every time you do +=. In this case, the delegate defines a signature that requires two parameters of type object and FileSystemEventArgs.
You can shorthand this using lambdas:
watcher.Changed += (sender, args) => {};
You need to look at the documentation for the event to work out the signature (or use an IDE like Visual Studio):
https://learn.microsoft.com/en-us/dotnet/api/system.io.filesystemwatcher.changed
When the File System Watcher needs to invoke the event (to inform consumers something has happened), it will call all of the delegates in the events invocation list, passing the sender and FileSystemEventArgs.
Let me know if I'm not understanding the question. So the watcher.Changed is using a delegate. So you are passing in a block of code to execute when they get a certain action on there point. You are just passing them in code to run.
Here is a little more simple example.
static void Main(string[] args)
{
DisplayString();
Console.ReadLine();
}
public static void DisplayString()
{
RunAction( (textToDisplay) =>
{
Console.Write(textToDisplay);
});
}
private static void RunAction(Action<string> action)
{
action("This Is A Test");
}
So in the Action<string>; it is saying my delegate is expecting a string. Which is passed in here
action("This Is A Test");
So In your example there code is
public delegate void FileSystemEventHandler(object sender, FileSystemEventArgs e);
Which means there code is passing you those two objects which you use through the (s, e)
So the watcher.Changed is declared
Blockquote
public event FileSystemEventHandler Changed;

Need to copy file after it's creation and changing

I need to copy a file after it's been created and changed twice.
I have no idea how to do it, except by using 3 FileSystemWachers. First one on creation and two for changing.
Is there any easier way?
The FileSystemWatcher has several events that you can use to listen to different types of events on the file system. There is also a NotifyFilter property which you can use to specify which event types it should monitor.
So you don't need to use three different watchers; one would suffice. You just need some form of counter to keep track of how many changes were made to the file. Here's a quick example:
Dictionary<string, int> changeCounter = new Dictionary<string, int>();
...
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = #"C:\Path\To\Some\Folder";
watcher.NotifyFilter = NotifyFilters.CreationTime | NotifyFilters.LastWrite;
watcher.Created += OnCreated;
watcher.Changed += OnChanged;
...
private void OnCreated(object source, FileSystemEventArgs e)
{
changeCounter.Add(e.FullPath, 0);
}
private void OnChanged(object source, FileSystemEventArgs e)
{
if (changeCounter.ContainsKey(e.FullPath))
{
changeCounter[e.FullPath]++;
if (changeCounter[e.FullPath] == 2)
{
CopyFile(e.FullPath);
}
}
}
This would only call CopyFile after the watcher detected a file creation event and two file change events for a single file. You may also want to modify handle deletions, too, in case you are worried about files being created once, edited, deleted, recreated, and edited—this would trigger CopyFile even though, technically, the file has only been edited once after it was created.

FileSystemWatcher, unsubscribe from the event

I'm fooling around with the FileSystemWatcher in 4.0. I find this very useful but am getting caught in a loop. I'm trying to monitor whenever an ini is changed and change it back to the correct default (long story) however the change event copying over the new file is causing it to drop into a loop ... Any Ideas > ? I played around with the idea of deleting and recreating thefile to avoid triggering the changed event but this leads to another set of issues with the program that I'd rather avoid. Also I'd imagine I could overwrite the text but this also poses the same issue. Thanks in advance for the help
static void Main() { Watch (#"\\NoFault2010\Lexis\Data\Setup\", "tmconfig.ini", true); }
static void Watch (string path, string filter, bool includeSubDirs)
{
using (var watcher = new FileSystemWatcher (path, filter))
{
watcher.Changed += FileChanged;
watcher.EnableRaisingEvents = true;
Console.WriteLine("Do Not Close ... \n\nThis is a Temporary Configuration Manager for Time Matters ... \n\n\nI'm Listening ............");
Console.ReadLine();
}
}
static void FileChanged (object o, FileSystemEventArgs e)
{
string _right_stuff = #"\\NOFAULT2010\Lexis\Data\Templates\Programs\tmconfig.ini";
string _working = #"\\NOFAULT2010\Lexis\Data\Setup\tmconfig.ini";
System.Threading.Thread.Sleep(2000);
File.Copy(_right_stuff, _working, true);
Console.WriteLine("File {0} has been {1}", e.FullPath, e.ChangeType);
MAIL_IT("SQLMail#lcjlawfirm.com", "TM Master.INI has been altered", "Check the Master INI and Yell At Ecopy Guy " + e.ChangeType + e.FullPath);
}
How would I unsubscribe from the event to avoid entering into this loop.
To temporarily disable the event while you're fiddling with the file yourself:
static void FileChanged (object o, FileSystemEventArgs e)
{
watcher.Changed -= FileChanged;
... correct the file here...
watcher.Changed += FileChanged;
}
Alternatively, you can use a guard variable to detect reentrant calls:
static bool reentrant = false;
static void FileChanged (object o, FileSystemEventArgs e)
{
if (reentrant)
return;
reentrant = true;
... correct the file here...
reentrant = false;
}
Note that you will also want to do exception handling within the method or your file watcher may become permanently disabled if a problem occurs.
I've written an application that depends on filesystemwatcher - and also, sometimes the fsw handler makes a change to a file.
I approached it in two ways - the first was to take the view that my code would be very quick in changing the file - so I did
fsw.EnableRaisingEvents = false;
//make my change
fsw.EnableRaisingEvents = true;
However, if you feel that other files might get changed during that time, you could log the time that you make the change and store that data somewhere...
say, Dictionary mapFileNameTimeChanged ...here you could store the file name...so in your handler you could do something like....
fsw_Changed(object sender, FileSystemEventArgs e)
{
lock (m_mapFileNameChanged)
{
if (m_mapFileNameChanged.ContainsKey(e.FullPath))
{
FileInfo fileInfo = new FileInfo(e.FullPath);
if (fileInfo.LastAccessTime == m_mapFileNameChanged[e.FullPath]
{
return;//not been changed since you last did something with it....
}
}
else
{
m_mapFileNameChanged.Remove(e.FullPath);//discard this now..it has changed since you last looked at it...need to look at it again!
}
}
//do things in your event handler...
lock (m_mapFileNameChanged)
{
// copy or change the file here...
FileInfo fileInfo = new FileInfo(e.FullPath);
m_mapFileNameChanged[strFullPathToFile] = fileInfo.LastAccessTime;
}
}
You could add a boolean (again at the class level) that you could use to track whether the changes were caused by you, and if so, just immediately exit your FileChanged method, ie:
static bool inEdit;
static void FileChanged (object o, FileSystemEventArgs e)
{
if (inEdit)
return;
inEdit = true;
// Do processing
inEdit = false;
}
Unsubscribe is easy, so I wonder if that was the question:
watcher.Changed -= FileChanged
Also, I would create some object to be SynchronizationObject for watcher. There is a problem that by default watcher raises events in new thread, and thus if you unsubscribe after new thread is created, you might run into the problems.
Also of note that FileSystemWatcher may raises multiple events for something you consider as single event, and it might influence functioning of your program.
If you make the watcher a class variable instead of a local variable, then your FileChanged method should be able to access it. Then you should be able to do something like
static void FileChanged (object o, FileSystemEventArgs e)
{
watcher.EnableRaisingEvents = false;
// Edit the file here
watcher.EnableRaisingEvents = true;
}

Categories