I need to process a number of directories and process files as they are created or renamed. To do this I created the following Watcher Class.
public class Watcher
{
FileSystemWatcher fileSystemWatcher = null;
public delegate void FileCreatedEventHandler(object sender, FileSystemEventArgs e);
public delegate void FileRenamedEventHandler(object sender, RenamedEventArgs e);
public delegate void LogEventHandler(object sender, String e);
public event FileCreatedEventHandler FileCreated;
public event FileRenamedEventHandler FileRenamed;
public event LogEventHandler LogEvent;
public String Directory { get; set; }
public String DAP { get; set; }
public String Filter { get; set; }
public Watcher()
{
fileSystemWatcher = new FileSystemWatcher();
}
public Watcher(String directory, String filter, String dap)
{
this.DAP = dap;
this.Directory = directory;
this.Filter = filter;
}
public void StartWatch()
{
if (fileSystemWatcher == null) fileSystemWatcher = new FileSystemWatcher();
fileSystemWatcher.Filter = Filter;
fileSystemWatcher.Path = Directory;
fileSystemWatcher.EnableRaisingEvents = true;
fileSystemWatcher.Created += FileSystemWatcher_Created;
fileSystemWatcher.Renamed += FileSystemWatcher_Renamed;
Log(String.Format("Watching Directory {0}", Directory));
}
private void FileSystemWatcher_Renamed(object sender, RenamedEventArgs e)
{
FileRenamed?.Invoke(sender, e);
}
private void FileSystemWatcher_Created(object sender, FileSystemEventArgs e)
{
FileCreated?.Invoke(sender, e);
}
private void Log ( String Message )
{
LogEvent?.Invoke(this, Message);
}
}
That in turn is used by the following function, which sets up a number of instances; one for each of the directories being watched.
public ProfileWatcher()
{
List<nhs_acquisition_profile> p = null;
List <Watcher> fwList = new List<Watcher>();
InitializeComponent();
try
{
if (!System.Diagnostics.EventLog.SourceExists("DirectoryScanner"))
{
System.Diagnostics.EventLog.CreateEventSource(
"DirectoryScanner","ProfileDirectoryScanner");
}
//
profileWatcherLog.Source = "DirectoryScanner";
profileWatcherLog.Log = "ProfileDirectoryScanner";
using (nhs_acquisition_profiles profiles = new nhs_acquisition_profiles(Properties.Settings.Default.DataConnection))
{
profileWatcherLog.WriteEntry("retrieving list of DAP Profiles");
p = profiles.Select<nhs_acquisition_profile>(null);
profileWatcherLog.WriteEntry(String.Format("Found {0} Directories to watch.", p.Count.ToString()));
foreach (nhs_acquisition_profile pfl in p)
{
Watcher w = null;
try
{
profileWatcherLog.WriteEntry(String.Format("Attempting to set-up watcher on {0} for DAP {1}",pfl.dap_file_location,pfl.dap_name));
w = new Watcher(pfl.dap_file_location, "*.*", pfl.dap_name);
profileWatcherLog.WriteEntry("Initialising Event Handlers");
// initialise event handlers
w.FileCreated += W_FileCreated;
w.FileRenamed += W_FileRenamed;
w.LogEvent += W_LogEvent;
profileWatcherLog.WriteEntry("Event Handlers initialised");
w.StartWatch();
profileWatcherLog.WriteEntry("Watch started....Adding to Watcher List");
// add the watcher to the list of watchers
fwList.Add(w);
profileWatcherLog.WriteEntry("Added to list of file watchers");
profileWatcherLog.WriteEntry(String.Format("Watching {0} for files matching *.* for DAP {1}",pfl.dap_file_location,pfl.dap_name));
}
catch
{
throw;
}
}
}
}
catch (Exception e)
{
profileWatcherLog.WriteEntry(String.Format("ERROR: {0}",e.Message),EventLogEntryType.Error);
}
finally
{
profileWatcherLog.WriteEntry("Profile Watcher setup");
}
}
Each time a file is created it should call one of the following
private void W_FileRenamed(object sender, RenamedEventArgs e)
{
try
{
profileWatcherLog.WriteEntry(String.Format("File Renamed From {0} to {1}", e.OldName, e.Name));
using (FileSystemWatcher watcher = ((System.IO.FileSystemWatcher)sender))
{
if (watcher != null)
Process(watcher.Path);
}
}
catch (System.Exception ex)
{
profileWatcherLog.WriteEntry(ex.Message, EventLogEntryType.Error);
}
finally
{
}
}
private void W_FileCreated(object sender, FileSystemEventArgs e)
{
try
{
profileWatcherLog.WriteEntry(String.Format("File Created {0}", e.Name));
using (FileSystemWatcher watcher = ((System.IO.FileSystemWatcher)sender))
{
if (watcher != null)
Process(watcher.Path);
}
}
catch ( System.Exception ex)
{
profileWatcherLog.WriteEntry(ex.Message, EventLogEntryType.Error);
}
finally
{
}
}
This is fine for the first time a file is dropped into a target directory. The trouble is it is resetting the EnableRaisingEvents once it returns from the
FileCreated?.Invoke(sender, e);
Line in the first listing above.
Nothing in the code seems to be doing this and when I try and reset it back after the FileCreated it then moans that the object has been disposed (even though the debugger shows it has a value)
The whole thing is then wrapped in a service, so that it runs all the time.
Related
I am writing a program which is supposed to detect when a USB serial device is plugged in and then log on to the new com port. The code below works wonderfully, but I have noticed in debugging the code and stepping through it that the event handler "DetectChange" fires twice. I'm not sure that this is normal, or an action of the debugger.
In any case, the code works, but I am new at event handling and I would like to make sure that I am not going to cause any issues as I add more code to actually read and write from the serial port.
(I got some of this code from stackoverflow, but I have misplaced my paper with names for attribution. If you see your code below, my heartfelt thanks.)
using System;
using System.IO.Ports;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Management;
using System.Threading;
namespace SerialTest
{
public partial class Form1 : Form
{
SerialMethods serialMethods = new SerialMethods();
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
loadCmdBox();
}
private void CmdBoxPort_SelectedIndexChanged(object sender, EventArgs e)
{
handleComPort();
}
private void handleComPort()
{
// Set the right port for the selected item.
// The portname is based on the "COMx" part of the string (SelectedItem)
string item = CmdBoxPort.SelectedItem.ToString();
// Search for the expression "(COM" in the "selectedItem" string
if (item.Contains("(COM"))
{
// Get the index number where "(COM" starts in the string
int indexOfCom = item.IndexOf("(COM");
// Set PortName to COMx based on the expression in the "selectedItem" string
// It automatically gets the correct length of the COMx expression to make sure
// that also a COM10, COM11 and so on is working properly.
string PortName = item.Substring(indexOfCom + 1, item.Length - indexOfCom - 2);
if (serialMethods._serialPort.IsOpen)
{
serialMethods._serialPort.Close();
serialMethods.Connect(PortName);
label5.Text = "Active Port: " + PortName;
}
else
{
serialMethods.Connect(PortName);
label5.Text = PortName;
}
}
else
return;
}
private void loadCmdBox()
{
// Get all serial (COM)-ports you can see in the devicemanager
ManagementObjectSearcher searcher = new ManagementObjectSearcher("root\\cimv2",
"SELECT * FROM Win32_PnPEntity WHERE ClassGuid=\"{4d36e978-e325-11ce-bfc1-08002be10318}\"");
// Sort the items in the combobox
CmdBoxPort.Sorted = true;
// Add all available (COM)-ports to the combobox
foreach (System.Management.ManagementObject queryObj in searcher.Get().Cast<ManagementObject>())
{
CmdBoxPort.Items.Add(queryObj["Caption"]);
}
SerialPortService.PortsChanged += (sender1, changedArgs) => DetectChange(changedArgs.EventType);
label2.Text = "";
label3.Text = "";
label4.Text = "";
}
protected Task<Task> getSerPorts()
{
CmdBoxPort.Text = "";
CmdBoxPort.Update();
if (!String.IsNullOrEmpty(CmdBoxPort.Text))
{
handleComPort();
return Task.FromResult(Task.CompletedTask);
}
else
{
loadCmdBox();
return Task.FromResult(Task.CompletedTask);
}
}
private void ExitButton_Click(object sender, EventArgs e)
{
SerialPortService.CleanUp();
this.Close();
}
private void RefreshButton_Click(object sender, EventArgs e)
{
refresh();
}
protected Task<Task> refresh()
{
label2.Text = "";
label3.Text = "";
label4.Text = "";
CmdBoxPort.Items.Clear();
getSerPorts();
return Task.FromResult(Task.CompletedTask);
}
protected virtual void DetectChange(EventType changedArgs)
{
if (changedArgs == EventType.Insertion)
{
try
{
Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
Task rr = (Task)Invoke(new Action(() => { refresh(); }));
}
catch (Exception ex) { MessageBox.Show("Exception at insertion invoke method " + ex, "Exception", MessageBoxButtons.OK); }
}
else if (changedArgs == EventType.Removal)
{
try
{
Task tr = (Task)Invoke(new Action( () => { getSerPorts(); }));
Task rr = (Task)Invoke(new Action(() => { refresh(); }));
}
catch (Exception ex) { MessageBox.Show("Exception at removal invoke method " + ex, "Exception", MessageBoxButtons.OK); }
}
return;
}
}
public static class SerialPortService
{
private static SerialPort _serialPort;
private static string[] _serialPorts;
private static ManagementEventWatcher arrival;
private static ManagementEventWatcher removal;
private static readonly SerialMethods SD = new SerialMethods();
static SerialPortService()
{
_serialPorts = SerialPort.GetPortNames();
MonitorDeviceChanges();
}
public static void CleanUp()
{
arrival.Stop();
removal.Stop();
}
public static event EventHandler<PortsChangedArgs> PortsChanged;
private static void MonitorDeviceChanges()
{
try
{
var deviceArrivalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 2");
var deviceRemovalQuery = new WqlEventQuery("SELECT * FROM Win32_DeviceChangeEvent WHERE EventType = 3");
arrival = new ManagementEventWatcher(deviceArrivalQuery);
removal = new ManagementEventWatcher(deviceRemovalQuery);
arrival.EventArrived += (o, args) => RaisePortsChangedIfNecessary(EventType.Insertion);
removal.EventArrived += (sender, eventArgs) => RaisePortsChangedIfNecessary(EventType.Removal);
// Start listening for events
arrival.Start();
removal.Start();
}
catch (ManagementException err)
{
MessageBox.Show("Management exception = " + err, "Info", MessageBoxButtons.OK);
}
}
private static void RaisePortsChangedIfNecessary(EventType eventType)
{
lock (_serialPorts)
{
var availableSerialPorts = SerialPort.GetPortNames();
if (eventType == EventType.Insertion)
{
var added = availableSerialPorts.Except(_serialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, added));
}
else if (eventType == EventType.Removal)
{
var removed = _serialPorts.Except(availableSerialPorts).ToArray();
_serialPorts = availableSerialPorts;
PortsChanged.Raise(null, new PortsChangedArgs(eventType, removed));
}
}
}
public static void Raise<T>(this EventHandler<T> handler, object sender, T args) where T : EventArgs
{
handler?.Invoke(sender, args);
}
}
public enum EventType
{
Insertion,
Removal,
}
public class PortsChangedArgs : EventArgs
{
private readonly EventType _eventType;
private readonly string[] _serialPorts;
public PortsChangedArgs(EventType eventType, string[] serialPorts)
{
_eventType = eventType;
_serialPorts = serialPorts;
}
public string[] SerialPorts => _serialPorts;
public EventType EventType => _eventType;
}
}
Just took a short look at this. It seems like getSerPorts() will always execute loadCmdBox() (CmdBoxPort.Text = ""; ... if (!String.IsNullOrEmpty(CmdBoxPort.Text))) that will attach a new event handler (previous attached event handlers will not be removed by attaching a new one).
You should either remove the existing event handler befor attaching a new one or only attach the event handler once.
I'm trying to fetch emails as soon as they arrive in my inbox using MailSystem.NET library. Everything works fine IMAP client gets connected but my NewMessageReceived event is never fired.
Please Help
Below is the code:
public static Imap4Client _imap = new Imap4Client();
public string SenderEmailAddress = System.Configuration.ConfigurationManager.AppSettings["EmailAddress"];
public string SenderEmailPassword = System.Configuration.ConfigurationManager.AppSettings["EmailPassword"];
public static Mailbox inbox = new Mailbox();
protected void Application_Start()
{
var worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(StartIdleProcess);
if (worker.IsBusy)
worker.CancelAsync();
worker.RunWorkerAsync();
}
private void StartIdleProcess(object sender, DoWorkEventArgs e)
{
try
{
if (_imap != null && _imap.IsConnected)
{
_imap.StopIdle();
_imap.Disconnect();
}
_imap = new Imap4Client();
_imap.NewMessageReceived += new NewMessageReceivedEventHandler(NewMessageReceived);
_imap.ConnectSsl("imap.gmail.com", 993);
_imap.Login(SenderEmailAddress, SenderEmailPassword);
inbox = _imap.SelectMailbox("inbox");
int[] ids = inbox.Search("UNSEEN");
inbox.Subscribe();
_imap.StartIdle();
}
catch (Exception ex)
{
}
}
public static void NewMessageReceived(object source, NewMessageReceivedEventArgs e)
{
int offset = e.MessageCount - 2;
Message message = inbox.Fetch.MessageObject(offset);
Debug.WriteLine("message subject: " + message.Subject);
// Do something with the source...
_imap.StopIdle();
}
I can't tell you the exact reason but it seems that interacting with the imapclient from the NewMessageReceived event just doesn't work.
In NewMessageReceived call _imap.StopIdle() then continue in your main execution flow and restart idle. Then use a boolean to drop out of the loop entirely.
private bool _stop = false;
private void StartIdle(object sender, DoWorkEventArgs e)
{
//Setup client
_imap = new Imap4Client();
_imap.NewMessageReceived += new NewMessageReceivedEventHandler(NewMessageReceived);
StartRepeatExecution();
}
public void StartRepeatExecution()
{
_imap.StartIdle();
if(_stop) return;
//Handle your new messages here! dummy code
var mailBox = _imap.SelectMailBox("inbox");
var messages = mailBox.SearchParse("").Last();
StartRepeatExecution();
}
public static void NewMessageReceived(object source, NewMessageReceivedEventArgs e)
{
//StopIdle will return to where _imap.StartIdle() was called.
_imap.StopIdle();
}
public void StopRepeatExecution()
{
_stop = true;
}
I have some files that are written to a folder.First there are 2 files written and after 10-20 minutes the next 2 files.
My question is:
Is there any possible way to tell the file system watcher
to wait until all 4 files are in the folder before executing my code?
According to #BugFinder 's suggestion I created something similar but didn't test. Hope it is useful:
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
namespace CustomFileWatcher
{
public class CustomFileWatcher : IDisposable
{
private FileSystemWatcher fileWatcher;
private IList<string> fileList;
private IList<string> createdFiles;
public event EventHandler FilesCreated;
protected void OnFilesCreated(EventArgs e)
{
var handler = FilesCreated;
if (handler != null)
handler(this, e);
}
public CustomFileWatcher(IList<string> waitForTheseFiles, string path)
{
fileList = waitForTheseFiles;
createdFiles = new List<string>();
fileWatcher = new FileSystemWatcher(path);
fileWatcher.Created += fileWatcher_Created;
}
void fileWatcher_Created(object sender, FileSystemEventArgs e)
{
foreach (var item in fileList)
{
if (fileList.Contains(e.Name))
{
if (!createdFiles.Contains(e.Name))
{
createdFiles.Add(e.Name);
}
}
}
if (createdFiles.SequenceEqual(fileList))
OnFilesCreated(new EventArgs());
}
public CustomFileWatcher(IList<string> waitForTheseFiles, string path, string filter)
{
fileList = waitForTheseFiles;
createdFiles = new List<string>();
fileWatcher = new FileSystemWatcher(path, filter);
fileWatcher.Created += fileWatcher_Created;
}
public void Dispose()
{
if (fileWatcher != null)
fileWatcher.Dispose();
}
}
}
Usage
class Program
{
static void Main(string[] args)
{
IList<string> waitForAllTheseFilesToBeCopied = new List<string>();
waitForAllTheseFilesToBeCopied.Add("File1.txt");
waitForAllTheseFilesToBeCopied.Add("File2.txt");
waitForAllTheseFilesToBeCopied.Add("File3.txt");
string watchPath = #"C:\OutputFolder\";
CustomFileWatcher customWatcher = new CustomFileWatcher(waitForAllTheseFilesToBeCopied, watchPath);
customWatcher.FilesCreated += customWatcher_FilesCreated;
}
static void customWatcher_FilesCreated(object sender, EventArgs e)
{
// All files created.
}
}
I want to run say 50 processes but since the machine will choke at 5, I want to run 5 processes at a time and and keep starting the next one as soon as one of the 5 running processes finish. Please advice what's a good way to do this?
I can wait on the last process I launch by Process.WaitForExit but that doesn't do the trick since what if one of the earlier 4 processes finishes first.
Thanks
I had to create a similar bottleneck while writing an app to handle hundreds of Powershell commands. I ended up creating a "queue" class that contained a list of objects. The objects were another custom class that contained the Process and a few events.
I'd loop through the processes, setting them up to be run, then add them to the Queue class. At the end I'd call a RunFirstScripts function that looped through the first 20 in the list and called a Start function, then removed it from the list.
I bound the Exit event to a handler that ran the next server and then removed it from the queue.
class ServerQueue
{
private List<Script> Servers = new List<Script>();
public void Add(Script Server)
{
Servers.Add(Server);
Server.Exited += cmd_Exited;
}
public void RunNextScript()
{
if (Servers.Count > 0)
{
Script ToRun = Servers[0];
Servers.Remove(ToRun);
ToRun.StartProcess();
}
}
public void StartFirstScripts()
{
byte Running = 0;
while (Servers.Count > 0 && Running <= 20)
{
RunNextScript();
Running++;
}
}
private void cmd_Exited(object sender, EventArgs e)
{
RunNextScript();
}
}
EDIT Here's some code for the Script:
class Script
{
public string Output
{
get
{
return pOutput;
}
}
public string Errors
{
get
{
return pErrors;
}
}
public bool IsRunning
{
get
{
return pIsRunning;
}
}
private string pOutput = "";
private string pErrors = "";
private bool pIsRunning = false;
public delegate void OutputEventHandler(Script sender, string Output, bool IsError);
public delegate void StatusEventHandler(Script sender);
public event OutputEventHandler OutputDataReceived;
public event OutputEventHandler ErrorDataReceived;
public event StatusEventHandler Started;
public event StatusEventHandler Exited;
private Process cmd;
public void StartProcess()
{
pIsRunning = true;
cmd.Start();
cmd.BeginOutputReadLine();
cmd.BeginErrorReadLine();
Started(this);
}
public void KillProcess()
{
if (IsRunning)
{
cmd.Kill();
}
}
public void SetupScript()
{
cmd = new Process();
//configure Process (but don't start it yet)
}
private void cmd_Exited(object sender, EventArgs e)
{
pIsRunning = false;
Exited(this);
//do other stuff
}
private void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
OutputDataReceived(this, e.Data, false);
//do stuff
}
private void cmd_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
ErrorDataReceived(this, e.Data, true);
//do stuff
}
}
There's a tiny race condition, if the planets align correctly you could attempt to start the same process twice but the second attempt should fail (as it's already running) and the way it's set up it's extremely unlikely to ever occur. You can add some extra error trapping if you like, there should probably be a try/catch in StartProcess (this is modified from my own solution and as such missing some context).
EDIT
Here is my Process to work with all these bits:
Process cmd = new Process();
cmd.StartInfo.FileName = "PowerShell.exe";
cmd.StartInfo.Arguments = "-executionpolicy unrestricted -file \"" + TempFile.FullName + "\" -pwd " + Pwd;
cmd.StartInfo.RedirectStandardOutput = true;
cmd.StartInfo.RedirectStandardError = true;
cmd.StartInfo.UseShellExecute = false;
cmd.StartInfo.CreateNoWindow = true;
cmd.EnableRaisingEvents = true;
cmd.OutputDataReceived += cmd_UpdateDataReceived;
cmd.ErrorDataReceived += cmd_ErrorDataReceived;
cmd.Exited += cmd_Exited;
here is the edited code for completeness. Full credit to Deadly-Bagel. Thanks,
class ServerQueue
{
private List<Script> Servers = new List<Script>();
public void Add(Script Server)
{
Servers.Add(Server);
Server.Exited += cmd_Exited;
}
public void RunNextScript()
{
if (Servers.Count > 0)
{
Script ToRun = Servers[0];
Servers.Remove(ToRun);
ToRun.StartProcess();
}
}
public void StartFirstScripts()
{
byte Running = 0;
while (Servers.Count > 0 && Running <= 20)
{
RunNextScript();
Running++;
}
}
private void cmd_Exited(object sender, EventArgs e)
{
RunNextScript();
}
}
class Script
{
public string Output
{
get
{
return pOutput;
}
}
public string Errors
{
get
{
return pErrors;
}
}
public bool IsRunning
{
get
{
return pIsRunning;
}
}
private string pOutput = "";
private string pErrors = "";
private bool pIsRunning = false;
public delegate void OutputEventHandler(Script sender, string Output, bool IsError);
public delegate void StatusEventHandler(Script sender, EventArgs e);
public event OutputEventHandler OutputDataReceived;
public event OutputEventHandler ErrorDataReceived;
public event StatusEventHandler Started;
public event StatusEventHandler Exited;
private Process cmd;
public void StartProcess()
{
pIsRunning = true;
cmd.Start();
cmd.BeginOutputReadLine();
cmd.BeginErrorReadLine();
Started(this, null);
}
public void KillProcess()
{
if (IsRunning)
{
cmd.Kill();
}
}
public void SetupScript()
{
cmd = new Process();
cmd.EnableRaisingEvents = true;
cmd.Exited += new EventHandler(cmd_Exited);
//configure Process (but don't start it yet)
}
private void cmd_Exited(object sender, EventArgs e)
{
pIsRunning = false;
Exited(this, null);
//do other stuff
}
private void cmd_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
OutputDataReceived(this, e.Data, false);
//do stuff
}
private void cmd_ErrorDataReceived(object sender, DataReceivedEventArgs e)
{
ErrorDataReceived(this, e.Data, true);
//do stuff
}
}
My code is
var files = Directory.GetFiles(this.DirectoryPath, this.File.FileMatchPattern, SearchOption.TopDirectoryOnly);
Where File.FileMatchPattern looks something like "FileName*.csv" and DirectoryPath is a unc share.
In dev this code executes fine but in uat we're getting the following exception:
ERROR 8 Guggenheim.Pipeline.Services.FileWatcher.WatchedFile - CheckForFile
System.IO.IOException: The process cannot access the file '\\il1tstbrsapp01\prodapps\FileWatcher\EPAMFileWatcherDropFolderTest\20140531_0171' because it is being used by another process.
at System.IO.__Error.WinIOError(Int32 errorCode, String maybeFullPath)
at System.IO.FileSystemEnumerableIterator`1.CommonInit()
at System.IO.FileSystemEnumerableIterator`1..ctor(String path, String originalUserPath, String searchPattern, SearchOption searchOption, SearchResultHandler`1 resultHandler, Boolean checkHost)
at System.IO.Directory.GetFiles(String path, String searchPattern, SearchOption searchOption)
at Guggenheim.Pipeline.Services.FileWatcher.WatchedFile.CheckForFile() in e:\VS\FixedIncome\Pipeline.Services\Guggenheim.Pipeline.Services.FileWatcher\Guggenheim.Pipeline.Services.FileWatcher\WatchedFile.cs:line 71
The weird thing is the path of the error is a directory not a file, it should be noted that that directory is being watched by a FileSystemWatcher.
I don't really care if the file is being accessed by another process, I just want to know if its there at this point. Is there any way to search a directory for files in .NET/C# without caring about locks?
Edit:
I'm watching the folder with the following code:
Watcher = new FileSystemWatcher(group.WatchDirectory);
Watcher.IncludeSubdirectories = true;
Watcher.NotifyFilter = NotifyFilters.DirectoryName;
Watcher.Created += Watcher_Created;
WatchedGroups = new List<WatchedGroup>();
Watcher.EnableRaisingEvents = true;
Then on folder created:
private void Watcher_Created(object sender, FileSystemEventArgs e)
{
WatchedGroup group = new WatchedGroup(Group, e.FullPath);
if (group.IsGroupReady)
group_GroupReady(group, new EventArgs());
else
{
group.GroupFailed += group_GroupFailed;
group.GroupReady += group_GroupReady;
WatchedGroups.Add(group);
}
}
WatchedGroup is the following:
public class WatchedGroup : IDisposable
{
public FileWatcherGroup Group { get; private set; }
private Timer GroupTimer { get; set; }
public List<WatchedFile> Files { get; private set; }
public string DirectoryPath { get; private set; }
public event EventHandler GroupReady;
public event EventHandler GroupFailed;
public bool IsGroupReady { get; private set; }
private readonly object sync = new object();
public WatchedGroup(FileWatcherGroup group, string directory)
{
this.Group = group;
this.GroupTimer = new Timer(Group.WaitTimeInMinutes * 60000);
this.GroupTimer.Elapsed += GroupTimer_Elapsed;
this.DirectoryPath = directory;
Files = Group.Files.Select(f => new WatchedFile(f, directory)).ToList();
CheckGroupReady();
if (IsGroupReady)
return;
foreach(var file in Files)
{
file.FileAvailable += File_FileAvailable;
}
this.GroupTimer.Start();
}
private void StopFiles()
{
foreach(var file in Files)
{
file.Stop();
}
this.GroupTimer.Stop();
}
private void CheckFiles()
{
foreach(var file in Files.Where(f => !f.IsFileAvailable))
{
file.CheckForFile();
}
}
private void CheckGroupReady()
{
lock (sync)
{
if (!IsGroupReady && Files.All(f => f.IsFileAvailable))
{
IsGroupReady = true;
StopFiles();
if (GroupReady != null)
GroupReady(this, new EventArgs());
}
}
}
private void File_FileAvailable(object sender, EventArgs e)
{
CheckGroupReady();
}
private void GroupTimer_Elapsed(object sender, ElapsedEventArgs e)
{
GroupTimer.Stop();
StopFiles();
CheckFiles();
CheckGroupReady();
if (!IsGroupReady && GroupFailed != null)
GroupFailed(this, new EventArgs());
}
public void Dispose()
{
this.StopFiles();
this.Files.Clear();
}
}
WatchedFile is the following:
public class WatchedFile
{
private static readonly ILog logger = LogManager.GetLogger(typeof(WatchedFile));
public WatcherFile File { get; private set; }
public bool IsFileAvailable { get; private set; }
public event EventHandler FileAvailable;
private FileSystemWatcher Watcher { get; set; }
public string DirectoryPath { get; private set; }
public string FilePath { get; private set; }
public WatchedFile(WatcherFile file, string directoryPath)
{
this.File = file;
this.DirectoryPath = directoryPath;
CheckForFile();
if (IsFileAvailable)
return;
try
{
Watcher = new FileSystemWatcher(directoryPath);
Watcher.Filter = File.FileMatchPattern;
Watcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.LastAccess | NotifyFilters.DirectoryName;
Watcher.Created += Watcher_Created;
Watcher.EnableRaisingEvents = true;
}
catch(Exception ex)
{
logger.Error("WatchedFile FileSystemWatcher", ex);
throw;
}
}
public void Stop()
{
if(Watcher != null)
Watcher.EnableRaisingEvents = false;
}
private void Watcher_Created(object sender, FileSystemEventArgs e)
{
this.FilePath = e.FullPath;
MarkFileAvailable();
}
private readonly object sync = new object();
private void MarkFileAvailable()
{
lock(sync)
{
if(!this.IsFileAvailable)
{
this.IsFileAvailable = true;
if (FileAvailable != null)
FileAvailable(this, new EventArgs());
}
}
}
public bool CheckForFile()
{
try
{
var files = Directory.GetFiles(this.DirectoryPath, this.File.FileMatchPattern, SearchOption.TopDirectoryOnly);
if (files.Length > 0)
{
this.FilePath = files.First();
MarkFileAvailable();
}
return this.IsFileAvailable;
}
catch(Exception ex)
{
logger.Error("CheckForFile", ex);
throw;
}
}
}
The purpose of this code is to allow an end user to drop a folder on a network share which is supposed to contain a set of files, defined by a pattern (FileName*.csv as an example) and if all the files are present within in a set amount of time it triggers an event that downstream systems pickup, if not it triggers a failure event.
You will get this if you use Directory.GetFiles on a folder that's currently being written to, which is the case here since you say files can be dropped at any time.
See here and here for various thoughts on the discussion.
You may need to consider leaving Directory.GetFiles out of the equation. Wrapping it in an exception handler with retries would be ugly and not performant.
The second link contains another approach where files are written to an intermediate folder, and only moved to the destination folder (on the same drive) once the write completes.