FileSystemwatcher for a list of files - c#

I have been following the guidance here FileSystemWatcher Changed event is raised twice.
However I have a list of files that I'm watching so if I delete 20 files together the event is called 20 times. This is expected and works fine.
How can I only have an event fired once for 20 "simultaneous" file changes (i.e How can I ignore all other file changes until the code in the first instance of Onchanged below has completed. Right now Onchanged is called 20 times.) ?
private void Main_Load(object sender, EventArgs e)
{
public static List<FileSystemWatcher> watchers = new List<FileSystemWatcher>();
UpdateWatcher();
}
public void OnChanged(object sender, FileSystemEventArgs e)
{
try
{
Logging.Write_To_Log_File("Item change detected " + e.ChangeType + " " + e.FullPath + " " + e.Name, MethodBase.GetCurrentMethod().Name, "", "", "", "", "", "", 2);
watchers.Clear();
foreach (FileSystemWatcher element in MyGlobals.watchers)
{
element.EnableRaisingEvents = false;
}
//Do some processing on my list of files here....
return;
}
catch (Exception ex)
{
// If exception happens, it will be returned here
}
finally
{
foreach (FileSystemWatcher element in MyGlobals.watchers)
{
element.EnableRaisingEvents = true;
}
}
}
public void UpdateWatcher() // Check Items
{
try
{
watchers.Clear();
for (int i = 0; i < MyGlobals.ListOfItemsToControl.Count; i++) // Loop through List with for
{
FileSystemWatcher w = new FileSystemWatcher();
w.Path = Path.GetDirectoryName(MyGlobals.ListOfItemsToControl[i].sItemName); // File path
w.Filter = Path.GetFileName(MyGlobals.ListOfItemsToControl[i].sItemName); // File name
w.Changed += new FileSystemEventHandler(OnChanged);
w.Deleted += new FileSystemEventHandler(OnChanged);
w.Created += new FileSystemEventHandler(OnChanged);
w.EnableRaisingEvents = true;
watchers.Add(w);
}
}
catch (Exception ex)
{
// If exception happens, it will be returned here
}
}

The key point here is what does "together" mean to you. after all the system does an independent delete operation for each, which would technically mean they are not all deleted at the exact same time, but if you just wanna be close, let's say if they are all deleted within 5 seconds of each other then we only want OnChange to fire once, you can do the following. Note that this doesn't handle the rename change notification. You weren't listening for it, so I assumed you don't need to.
you may wanna change the 5 seconds window to a small window depending on your use.
class SlowFileSystemWatcher : FileSystemWatcher
{
public delegate void SlowFileSystemWatcherEventHandler(object sender, FileSystemEventArgs args);
public event SlowFileSystemWatcherEventHandler SlowChanged;
public DateTime LastFired { get; private set; }
public SlowFileSystemWatcher(string path)
: base(path)
{
Changed += HandleChange;
Created += HandleChange;
Deleted += HandleChange;
LastFired = DateTime.MinValue;
}
private void SlowGeneralChange(object sender, FileSystemEventArgs args)
{
if (LastFired.Add(TimeSpan.FromSeconds(5)) < DateTime.UtcNow)
{
SlowChanged.Invoke(sender, args);
LastFired = DateTime.UtcNow;
}
}
private void HandleChange(object sender, FileSystemEventArgs args)
{
SlowGeneralChange(sender, args);
}
protected override void Dispose(bool disposing)
{
SlowChanged = null;
base.Dispose(disposing);
}
}

Related

Having problems with the flow of my c# program

I'm writing a program that has multiple event handlers in it, new territory for me. One event handler watches a folder and when a file is moved into it it opens the file, gets one piece of information out of the file, stores that data in a list of objects and then moves the file to another folder. The second event handler is based off of a timer where it's interval is set for 10 seconds. When the timer elapses it loops through the list I've loaded up and checks to see if the timestamp of when the item was loaded to the list is more than 1 minute old and if it is then it just displays a message to the console. I have two main problems: first) the message that should be displayed to the console once a minute is displaying more often than it should (about once every 15 seconds as opposed to once a minute) and 2) when I try to debug the program and it gets to the timer event handler it jumps all over the place that doesn't follow any logical path to me (I'm probably wrong). Please, can someone help me out? Any help would be much appreciated.
Code:
public class fileObject
{
public string strPONumber { get; set; }
public DateTime dtArrival { get; set; }
}
private static System.Timers.Timer aTimer;
public static void Main()
{
Run();
}
[PermissionSet(SecurityAction.Demand, Name = "FullTrust")]
public static void Run()
{
Console.WriteLine("redefined");
List<fileObject> lstPONums = new List<fileObject>();
// Create a new FileSystemWatcher and set its properties.
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = #"C:\Users\z088476\Desktop\FolderA\";
// Only watch text files.
watcher.Filter = "*";
// Add event handlers.
Console.WriteLine("before event");
watcher.Created += new FileSystemEventHandler((sender, e) => OnChanged(sender, e, lstPONums));
aTimer = new System.Timers.Timer();
aTimer.Elapsed += new ElapsedEventHandler((sender, e) => OnTimedEvent(sender, e, lstPONums));
aTimer.Interval=10000;
aTimer.Enabled = true;
Console.WriteLine("Press \'q\' to quit the sample.");
while (Console.Read() != 'q') ;
}
// Define the event handlers.
private static void OnChanged(object source, FileSystemEventArgs e, List<fileObject> lstPONumbers)
{
fileObject fObj = new fileObject();
string strLine = "";
using (StreamReader sr = new StreamReader(e.FullPath))
{
strLine = sr.ReadLine();
}
string strDelim = strLine.Substring(3,1);
List<string> lstSplit = new List<string>();
lstSplit = strLine.Split(Convert.ToChar(strDelim)).ToList();
fObj.strPONumber = lstSplit[30];
fObj.dtArrival = DateTime.Now;
lstPONumbers.Add(fObj);
File.Move(e.FullPath, #"C:\Users\z088476\Desktop\FolderC\" + e.Name);
}
private static void OnTimedEvent(object source, ElapsedEventArgs e, List<fileObject> lstPONumbers)
{
if (lstPONumbers.Count != 0)
{
foreach (fileObject fo in lstPONumbers)
{
DateTime dtnow = DateTime.Now;
TimeSpan duration = DateTime.Now - fo.dtArrival;
if (duration.TotalMinutes > 1)
{
Console.WriteLine("IT IS MORE THAN 1 MIN!!!!");
}
}
}
}

C# BackgroundWorker ProgressChanged doesn't get fired until end of function

I have a method in my class that has some loops inside.
Main purpose of this method is converting some files so I put a progressbar in my form that should get updated after each file has been converted.
I tried every possible combination and I read everything I could but I couldn't solve this issue.
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
converterProgressBar.Value = e.ProgressPercentage;
}
is called only after the main loop of my method has been executed.
This is my method:
public string Convert()
{
convertBtn.Enabled = false;
bw.WorkerReportsProgress = true;
bw.WorkerSupportsCancellation = true;
bw.DoWork += new DoWorkEventHandler(bw_DoWork);
bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
totalCount = files.length;
bw.RunWorkerAsync();
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
foreach (string file in files)
{
countFile++;
if (chk.Checked)
{
class1.DoJob();
}
using (// some code))
{
using (//some other code))
{
try
{
using (// again some code)
{
// job executing
}
}
catch (exception
{
}
}
}
convertedVideosL.Text = txtToUpdate;
convertedVideosL.Refresh();
}
countFile = countFile + 1;
MessageBox.Show("Done");
countFile = -1;
return outputFile;
}
And here are the BackgroundWorker Event Handlers:
void bw_DoWork(object sender, DoWorkEventArgs e)
{
for (int i = 0; i <= totalCount; i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
}
else
{
int progress = Convert.ToInt32(i * 100 / totalCount);
(sender as BackgroundWorker).ReportProgress(progress, i);
}
}
}
void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
converterProgressBar.Value = e.ProgressPercentage;
}
void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled == false)
{
convertedVideosL.Text = "Finished!";
}
else
{
convertedVideosL.Text = "Operation has been cancelled!";
}
}
But I cannot get to update the progress bar for every file that is converted.
It waits for the foreach loop to end and then calls bw_ProgressChanged.
If I put RunWorkerAsync() inside the foreach loop an exception is thrown that says the BackgroundWorker is busy and cannot execute other tasks.
It seems to me obvious that DoWork() only executes a for loop then it shouldn't be aware of the conversion going on but ProgressChanged should be fired by ReportProgress(progress,i).
Could please someone explain me why and help me with a solution?
Thanks!
Currently the conversion is not executed by the instance of the BackgroundWorker type. The conversion should be called from the DoWork event handler.
Please consider extracting the conversion-related functionality:
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
foreach (string file in files)
{
// Details...
}
into the separate method. After that just call the method from the DoWork event handler.
Pseudo-code to demonstrate the idea:
public void StartConversion()
{
...
TWorkerArgument workerArgument = ...;
worker.RunWorkerAsync(workerArgument);
// No message box here because of asynchronous execution (please see below).
}
private void BackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
// Get the BackgroundWorker that raised this event.
BackgroundWorker worker = sender as BackgroundWorker;
e.Result = Convert(worker, (TWorkerArgument)e.Argument);
}
private static TWorkerResult Convert(BackgroundWorker worker, TWorkerArgument workerArgument)
{
if (!Directory.Exists(folder))
{
Directory.CreateDirectory(folder);
}
foreach (string file in files)
{
// Details...
worker.ReportProgress(percentComplete);
}
return ...;
}
private void BackgroundWorkerRunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// Show the message box here if required.
}
Please replace the TWorkerArgument and TWorkerResult types appropriately.
Also, please refer to the example which uses the BackgroundWorker class for the additional details: How to: Implement a Form That Uses a Background Operation, MSDN.

filewatcher watch several files but handle them differently

I need to watch several file at different time and sometimes at the same time.
I am using this as a test:
namespace FilewatcherTest
{
public partial class Form1 : Form
{
private System.IO.FileSystemWatcher FSWatcherTest;
public Form1()
{
InitializeComponent();
FSWatcherTest = new FileSystemWatcher();
EventHandling();
FSWatcherTest.Path = #"d:\tmp";
FSWatcherTest.Filter = "file.txt";
// Begin watching.
FSWatcherTest.EnableRaisingEvents = true;
}
protected void EventHandling()
{
FSWatcherTest.Changed += FSWatcherTest_Changed;
FSWatcherTest.Deleted += FSWatcherTest_Deleted;
FSWatcherTest.Renamed += FSWatcherTest_Renamed;
FSWatcherTest.Created += FSWatcherTest_Created;
}
private void FSWatcherTest_Changed(object sender, System.IO.FileSystemEventArgs e)
{
WriteToLog("File Changed");
}
private void FSWatcherTest_Created(object sender, System.IO.FileSystemEventArgs e)
{
WriteToLog("File Created");
}
private void FSWatcherTest_Deleted(object sender, System.IO.FileSystemEventArgs e)
{
WriteToLog("File Deleted");
}
private void FSWatcherTest_Renamed(object sender, System.IO.RenamedEventArgs e)
{
WriteToLog("File Renamed");
}
private void WriteToLog(string message)
{
using (var sw = new StreamWriter(#"d:\tmp\service.log", true))
{
sw.WriteLine(string.Format("{0} {1}", DateTime.Now,message));
}
}
}
}
Of course I'll change the hardcoded paths once I have something in place since this is going into a service I created.
My question is, can I use the same file watcher or should I use a unique one for each file?
If I use the same one, how do I know which file is raising the event?
Thanks!!
EDIT
Sorry I haven't used filesystemwatcher before and didn't know it mattered but the files will be in different directories and not of the same file type.
can I use the same file watcher or should I use a unique one for each file?
In your case, I don't think there is a reason to create a new instance of FileSystemWatcher for every file you're watching. Yes, you can use the same one. You can use a filter such as "*.txt" or whatever you need to watch a set of files...
If I use the same one, how do I know which file is raising the event?
The FileSystemEventArgs has a Name property which returns the name of the file that triggered the event.
So for example:
private void FSWatcherTest_Created(object sender, System.IO.FileSystemEventArgs e)
{
string fileName = e.Name;
WriteToLog("File Created: " + fileName);
}

cross-thread calls?

This is mt first time trying to write a not web based program, and my first time writing anything in C#.
I need a program that monitors folders, but I can't get it to work.
I have used the example from this post Using FileSystemWatcher with multiple files but is trying to make it a form.
My current problem comes in the ProcessQueue function where fileList apparently is defined in another thread.
Whenever a file is actually submitted to the watched folder I get an error that using fileList is a cross thread call
Can anyone explain this error to me, and how to fix it?
namespace matasWatch
{
public partial class Form1 : Form
{
private int n = 1;
private bool isWatching = false;
private List<string> filePaths;
private System.Timers.Timer processTimer;
private string watchedPath;
private FileSystemWatcher watcher;
public Form1()
{
filePaths = new List<string>();
watchedPath = "C:\\Users\\username\\Desktop\\test";
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (!isWatching)
{
button1.Text = "Stop";
isWatching = true;
watcher = new FileSystemWatcher();
watcher.Filter = "*.*";
watcher.Created += Watcher_FileCreated;
watcher.Error += Watcher_Error;
watcher.Path = watchedPath;
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
}
else {
button1.Text = "Watch";
isWatching = false;
watcher.EnableRaisingEvents = false;
watcher.Dispose();
watcher = null;
}
}
private void Watcher_Error(object sender, ErrorEventArgs e)
{
// Watcher crashed. Re-init.
isWatching = false;
button1_Click(sender, e);
}
private void Watcher_FileCreated(object sender, FileSystemEventArgs e)
{
filePaths.Add(e.FullPath);
if (processTimer == null)
{
// First file, start timer.
processTimer = new System.Timers.Timer(2000);
processTimer.Elapsed += ProcessQueue;
processTimer.Start();
}
else{
// Subsequent file, reset timer.
processTimer.Stop();
processTimer.Start();
}
}
private void ProcessQueue(object sender, ElapsedEventArgs args)
{
try
{
fileList.BeginUpdate();
foreach (string filePath in filePaths)
{
fileList.Items.Add("Blaa");
}
fileList.EndUpdate();
filePaths.Clear();
}
finally
{
if (processTimer != null)
{
processTimer.Stop();
processTimer.Dispose();
processTimer = null;
}
}
}
}
}
I assume that fileList is a windows forms control. The ProcessQueue method is called from a timer thread which is by default a background thread. The fileList control resides in the UI thread. You need to use the Invoke() method of the form passing it in a delegate the updates the fileList control.
Invoke(new Action(() =>
{
fileList.BeginUpdate();
foreach (string filePath in filePaths)
{
fileList.Items.Add("Blaa");
}
fileList.EndUpdate();
filePaths.Clear();
}));
Try using System.Windows.Forms.Timer instead of System.Timers.Timer so the timer tick event is executed on the UI thread.
See here for more details.

Binding Events In another class

this is my first C# post.
I have a question on event binding.
I have a FileWatcher which I'd like to bind to functions that are defined in a separate class called FileWatcherEvents.
I don't want the events to be declared in the Program class, how can this be done?
As you can see, I try to bind the events for Created and Deleted.
The problem is that these events are not called when I delete or create a file in the folder. But when I declare the event handlers in the Program class, it does work.
Any help or insights is appreciated.
Program
using System.IO;
class Program : ServiceBase
{
private FileSystemWatcher _watcher;
public Program()
{
FileWatcherEvents fwe = new FileWatcherEvents();
this._watcher = new FileSystemWatcher();
((System.ComponentModel.ISupportInitialize)(this._watcher)).BeginInit();
//
// _watcher
//
this._watcher.EnableRaisingEvents = true;
this._watcher.Filter = "*.txt";
this._watcher.NotifyFilter =
((NotifyFilters)(((((NotifyFilters.FileName
| NotifyFilters.DirectoryName)
| NotifyFilters.LastWrite)
| NotifyFilters.LastAccess)
| NotifyFilters.CreationTime)));
this._watcher.Path = "T:\\out\\";
this._watcher.Deleted += new FileSystemEventHandler(fwe.ShipmentFileCreated);
this._watcher.Created += new FileSystemEventHandler(fwe.FileDeleted);
((System.ComponentModel.ISupportInitialize)(this._watcher)).EndInit();
}
static void Main(string[] args)
{
Program prg = new Program();
Console.Write(FileManager.getNewestFile("T:\\out\\"));
while (Console.Read() != 'q') ;
}
}
FileWatcherEvents
class FileWatcherEvents
{
public void ShipmentFileCreated(object sender, FileSystemEventArgs e)
{
Console.WriteLine("CREATED: " + sender.ToString() + e.ToString());
}
public void FileDeleted(object sender, FileSystemEventArgs e)
{
Console.WriteLine("DELETED: " + sender.ToString() + e.ToString());
}
}
I believe you would need to declare fwe in a larger scope, like at the Program level instead of inside the Program constructor. Otherwise the object will go away, and possibly all the events that lead to it as well (never been entirely clear on what happens to the functions that handle events on an instance when the instance goes away, but the events could still occur, but it's very possible they will no longer run).
Edit:
I got your code to work with some minor adjustments. Mainly I had to move EnableRaisingEvents to the end of the block because .NET throws an exception if you do it before setting the path. How did you not see that exception?
class Program
{
private FileSystemWatcher _watcher;
public Program()
{
FileWatcherEvents fwe = new FileWatcherEvents();
this._watcher = new System.IO.FileSystemWatcher();
this._watcher.Filter = "*.txt";
this._watcher.NotifyFilter = ((System.IO.NotifyFilters)(((((
System.IO.NotifyFilters.FileName | System.IO.NotifyFilters.DirectoryName)
| System.IO.NotifyFilters.LastWrite) | System.IO.NotifyFilters.LastAccess)
| System.IO.NotifyFilters.CreationTime)));
this._watcher.Path = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
this._watcher.Deleted += new System.IO.FileSystemEventHandler(fwe.ShipmentFileCreated);
this._watcher.Created += new System.IO.FileSystemEventHandler(fwe.FileDeleted);
this._watcher.EnableRaisingEvents = true;
Console.ReadLine();
}
public static void Main()
{
Program prg = new Program();
using (System.IO.StreamWriter w = new System.IO.StreamWriter(
System.IO.Path.Combine(
Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "TestFile.txt"), false))
{
w.WriteLine(DateTime.Now.ToLongTimeString());
}
Console.ReadLine();
}
}
class FileWatcherEvents
{
public void ShipmentFileCreated(object sender, FileSystemEventArgs e)
{
Console.WriteLine("CREATED: " + sender.ToString() + e.ToString());
}
public void FileDeleted(object sender, FileSystemEventArgs e)
{
Console.WriteLine("DELETED: " + sender.ToString() + e.ToString());
}
}
Turns out that the code works fine, the events fired, but the functions weren't because of the *.txt filter in the private FileSystemWatcher object.

Categories