My program needs to monitor multiple locations, but trigger the same code for each location. As a single FileSystemWatcher can't monitor multiple locations, but is it possible to create multiple instances of it and pass in a folder path for each?
I can't hard code each FileSystemWatcher as more and more locations will need to be added in time and this needs to be done by the end users, as it is highly impractical for me to have to manually hard code a new FileSystemWatcher each time. So my plan was to have the folder paths saved to a file and the program just creates a FileSystemWatcher for each path in the list. But I have no idea if this is possible in the slightest.
Going on the Factory Method Pattern suggestion here the attempt:
I get the errors: "'List' does not contain a definition for 'add'
public void StartWatchers()
{
string[] ArrayPaths = new string[2];
List<FileSystemWatcher> watchers = new List<FileSystemWatcher>();
ArrayPaths[0] = #"K:\Daily Record Checker\Test\Test1";
ArrayPaths[1] = #"K:\Daily Record Checker\Test\Test2";
int i = 0;
foreach (String String in ArrayPaths)
{
watcher.add(MyWatcherFatory(ArrayPaths[i]));
i++;
}
//Do other stuff....
//....
//Start my watchers...
foreach (FileSystemWatcher watcher in watchers)
{
Watcher.EnableRaisingEvents = true;
i++;
}
}
FileSystemWatcher MyWatcherFatory(string path)
{
FileSystemWatcher watcher = new FileSystemWatcher(path);
watcher.Changed += Watcher_Created;
watcher.Path = path;
watcher.Filter = "*.csv";
return watcher;
}
private void Watcher_Created(object sender, FileSystemEventArgs e)
{
System.Threading.Thread.Sleep(1000);
FileInfo fileInfo = new FileInfo(e.FullPath);
if (!IsFileLocked(fileInfo))
{
CheckNumberOfRecordsInFile(e.FullPath);
}
}
Use the factory method pattern.
FileSystemWatcher MyWatcherFatory(string path, object additionalParameters)
{
FileSystemWatcher watcher = new FileSystemWatcher(path);
watcher.Changed += myWatcherChangedMethod;//Attach them to the same listeners,
//Set additional parameters...
return watcher.
}
EDIT: Based on the information you further provided:
public void StartWatchers()
{
string[] ArrayPaths = new string[2];
List<FileSystemWatcher> watchers = new List<FileSystemWatcher>();
ArrayPaths[0] = #"K:\Daily Record Checker\Test\Test1";
ArrayPaths[1] = #"K:\Daily Record Checker\Test\Test2";
int i = 0;
foreach (String String in ArrayPaths)
{
watchers.Add(MyWatcherFatory(ArrayPaths[i]));
i++;
}
//Do other stuff....
//....
//Start my watchers...
foreach (FileSystemWatcher watcher in watchers )
{
watcher.EnableRaisingEvents = true;;
i++;
}
}
FileSystemWatcher MyWatcherFatory(string path)
{
FileSystemWatcher watcher = new FileSystemWatcher(path);
watcher.Changed += Watcher_Created;
watcher.Path = path;
watcher.Filter = "*.csv";
return watcher;
}
private void Watcher_Created(object sender, FileSystemEventArgs e)
{
System.Threading.Thread.Sleep(1000);
FileInfo fileInfo = new FileInfo(e.FullPath);
if (!IsFileLocked(fileInfo))
{
CheckNumberOfRecordsInFile(e.FullPath);
}
}
Related
I posted a question here but deleted it after I found a rather tedious solution
I am trying to write an app that can monitor multiple folders. I looked at the following solutions:
FIleSystemWatcher multiple folders (Dynamically)
Create multiple instances of the same FileSystemWatcher
Monitor multiple folders using FileSystemWatcher
I tried all their solutions. But what would happen is that it only worked for local folders. Not network drives.
My last implementation was a combination of all three:
public static void StartMultipleWatchers(string path)
{
string[] paths = path.Split(',');
foreach (string folderPath in paths)
{
try
{
string folderPathtrim = folderPath.Trim();
WatchFile(folderPathtrim);
}
catch(Exception ex)
{
Logger.Error(ex);
}
}
}
private static void WatchFile(string monitoredDir)
{
FileSystemWatcher fsw = new FileSystemWatcher(monitoredDir, "*.gz");
fsw.Changed += new FileSystemEventHandler(OnChanged);
fsw.Created += new FileSystemEventHandler(OnCreated);
fsw.EnableRaisingEvents = true;
fsw.IncludeSubdirectories = true;
Logger.Info($"Started loop Monitor of Folder: {monitoredDir}");
}
private static void OnCreated(object sender, FileSystemEventArgs e)
{
FileInfo fileInfo = new FileInfo(e.FullPath);
string value = $"Created: {e.FullPath}";
Logger.Info(value);
}
Again this solution only worked for local folder. Or if I only made one watcher that watched one network folder.
Then I tried this very tedious solution:
public static void TestManualWatchers()
{
var fsw1 = new FileSystemWatcher(#"\\lap.org.com\tool_data_odp_ws\metrology\CIM\DATA_READY\");
var fsw2 = new FileSystemWatcher(#"C:\TestPath\");
fsw1.Changed += OnChanged;
fsw1.Created += OnCreated;
fsw1.EnableRaisingEvents = true;
fsw1.IncludeSubdirectories = true;
fsw2.Changed += OnChanged;
fsw2.Created += OnCreated;
fsw2.EnableRaisingEvents = true;
fsw2.IncludeSubdirectories = true;
Logger.Info($"Started watching manual double files");
}
Where I manually create each watcher and it's own properties. What is the difference between this tedious way and the dynamic above?
Is there a way to actually dynamically create individual watchers?
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();
}
}
}
}
I am trying to monitor a directory for new files in it with the FileSystemWatcher Class.
My Problem is that the event doesn't get triggered. My watcher class is:
public class Filewatcher
{
private FileSystemWatcher watcher;
public Filewatcher(string path, string filefilter)
{
this.watcher = new FileSystemWatcher();
this.watcher.Path = path;
this.watcher.NotifyFilter = NotifyFilters.FileName; //| NotifyFilters.LastWrite | NotifyFilters.LastAccess
this.watcher.Filter = filefilter;
this.watcher.Created += new FileSystemEventHandler(OnChanged);
}
public void start_watching()
{
//this.watcher.IncludeSubdirectories = true;
this.watcher.EnableRaisingEvents = true;
Console.WriteLine("Press Enter to quit\r\n");
Console.ReadLine();
}
private static void OnChanged(object source, FileSystemEventArgs e)
{
//FileInfo objFileInfo = new FileInfo(e.FullPath);
//if (!objFileInfo.Exists) return;
Console.WriteLine("got the change\r\n");
global_functions.perform_action(Global_Vars.thereader.read(e.FullPath), Global_Vars.thepattern);
}
}
Operating System is Win 7 Prof x64
You don't make a call to start_watching(). Add a call to that and it may work better.
I made a small winforms application to monitor a certain folder for new pdf files, if a new pdf file is created in the particulair folder it will copy it to an other location.
The problem i'm having is that the filesystemwatcher creates double/multiple entries in my listbox, how can i solve this?
namespace Scanmonitor
{
public partial class Form1 : Form
{
FileSystemWatcher watcher = new FileSystemWatcher();
DateTime lastRead = DateTime.MinValue;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
FileWatch();
}
public void FileWatch()
{
watcher.Path = #"C:\Scanner";
watcher.NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName | NotifyFilters.LastWrite;
watcher.Filter = "*.pdf";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
public void OnChanged(object source, FileSystemEventArgs e)
{
scannerListBox.Items.Add(e.FullPath);
scannerListBox.SelectedIndex = scannerListBox.Items.Count - 1;
FileMove(scannerListBox.SelectedItem.ToString());
}
public void FileMove(string filePath)
{
try
{
System.IO.File.Copy(filePath, #"\\share\Data\Scans op OCE 600\" + Path.GetFileName(filePath));
}
catch (Exception ex)
{
ToolLabel.Text = ex.Message.ToString();
}
}
}
}
}
Got it working.
public void OnChanged(object source, FileSystemEventArgs e)
{
try
{
watcher.EnableRaisingEvents = false;
FileInfo objFileInfo = new FileInfo(e.FullPath);
if (!objFileInfo.Exists) return;
System.Threading.Thread.Sleep(5000);
FileInfo fileinformatie = new FileInfo(e.FullPath);
string strCreateTime = fileinformatie.CreationTime.ToString();
string strCreateDate = fileinformatie.CreationTime.ToString();
strCreateTime = strCreateTime.Remove(strCreateTime.LastIndexOf(" "));
strCreateDate = strCreateDate.Remove(0,strCreateDate.LastIndexOf(" "));
ProcessAllFiles(e.FullPath, strCreateTime, strCreateDate);
}
catch (Exception ex)
{
ToolLabel.Text = ex.Message.ToString();
}
finally
{
watcher.EnableRaisingEvents = true;
}
}
You need to keep track of files (in a collection or dictionary) that already raised an event by the FileSystemWatcher. According to MSDN
Common file system operations might raise more than one event. For example, when a file is moved from one directory to another, several OnChanged and some OnCreated and OnDeleted events might be raised. Moving a file is a complex operation that consists of multiple simple operations, therefore raising multiple events. Likewise, some applications (for example, antivirus software) might cause additional file system events that are detected by FileSystemWatcher.
public void OnChanged(object source, FileSystemEventArgs e)
{
try
{
watcher.EnableRaisingEvents = false;
FileInfo objFileInfo = new FileInfo(e.FullPath);
if (!objFileInfo.Exists) return;
System.Threading.Thread.Sleep(5000);
FileInfo fileinformatie = new FileInfo(e.FullPath);
string strCreateTime = fileinformatie.CreationTime.ToString();
string strCreateDate = fileinformatie.CreationTime.ToString();
//Ignore this, only for my file information.
strCreateTime = strCreateTime.Remove(strCreateTime.LastIndexOf(" "));
strCreateDate = strCreateDate.Remove(0,strCreateDate.LastIndexOf(" "));
ProcessAllFiles(e.FullPath, strCreateTime, strCreateDate);
}
catch (Exception ex)
{
ToolLabel.Text = ex.Message.ToString();
}
finally
{
watcher.EnableRaisingEvents = true;
}
}