I just started with C#, and i tried to create create a FileWatcher, which should print the content of a File, if it is changed:
{
public static void watch()
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = "Path";
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Filter = "Filter";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
}
public static void OnChanged(object source, FileSystemEventArgs e)
{
using (TextReader r = File.OpenText("Path")) {
while ((s = r.ReadLine()) != null) {
Console.WriteLine(s);
}
r.Close();
}
}
static void Main()
{
watch();
}
}
So far the FileWatcher is working fine, but if I try to print the content it works once and no matter how long I wait the programm will stop working on the second change.
As far as I understood the "using" statement should free the file. The close command does not change anything at all.
The file is a very small text file and should not be a problem.
Is there anyway to force the program to free the File?
The following code works fine:
using System;
using System.IO;
namespace FileReadTest
{
internal class Program
{
public static FileSystemWatcher watch()
{
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = "d:\\";
watcher.NotifyFilter = NotifyFilters.LastWrite;
watcher.Filter = "test.txt";
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.EnableRaisingEvents = true;
return watcher;
}
public static void OnChanged(object source, FileSystemEventArgs e)
{
string s;
using (StreamReader r = new StreamReader(File.Open("d:\\test.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
while ((s = r.ReadLine()) != null)
{
Console.WriteLine(s);
}
}
}
static void Main()
{
var watcher = watch();
Console.ReadKey();
watcher.Dispose();
}
}
}
Note that I've changed file reading routine to avoid some reading problems when a file is opened by an other program. FileSystemWatcher will also not get out of scope and will not be disposed accidentally.
I think instead of watcher.EnableRaisingEvents = true, in your case want to use watcher.WaitForChanged(WatcherChangeTypes.All), to make your program wait for changes indefinitely.
BTW, the statement r.Close() is redundant because you are already implicitly call Dispose() via using, which in turn calls Close().
EDIT: To be more specific: WaitForChanged of course just waits for one change and then returns, so if you want to wait for more changes, you can use a loop. Note that no event handler is needed if you use it this way.
while(true)
{
watcher.WaitForChanged(WaitForChanged.All);
// Do stuff with the changed file here, no event handler needed
using(var sr = new StreamReader(filePath))
{
// ...
}
}
the first thing to do is debug and verify if your method OnChanged gets called only once or also at the following edits of the monitored file, then you already know if the issue is with the lifetime / scope of the watcher or somewhere else.
Is this a console application? does it close or stays open after you call the watch method in the main?
You need to dispose the FileSystemWatcher.
public static void OnChanged(object source, FileSystemEventArgs e)
{
using (TextReader r = File.OpenText("Path")) {
while ((s = r.ReadLine()) != null) {
Console.WriteLine(s);
}
r.Close();
}
File((FileSystemWatcher)sender).Dispose();
}
Related
I have a multi-threaded program (3-4 threads). All the threads depend on a couple of parameters which are specified in an XML file.
Since the parameters in the XML file may be changed at any time by a user therefore, the different threads need to be notified about it and need to get the updated copy of parameters.
To monitor the changes in the XML file, I am using a FileWatcher as per the MSDN documentation.
clas ReadXML
{
//parameters
private static string Param1 = "";
private static string Param2 = "";
public static void ReadXmlParameters()
{
XmlDocument xDoc = new XmlDocument();
try
{
xDoc.Load(_ParameterFileDirrectory + #"\" + _ParameterFileDirrectory);
//parameters
Param1 = (xDoc.DocumentElement.SelectSingleNode("/Parameters/SetOne/IpAddress")).InnerText;
Param2 = (xDoc.DocumentElement.SelectSingleNode("/Parameters/SetOne/Username")).InnerText;
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
}
}
public static void CreateXMLWatcher()
{
try
{
// Create a new FileSystemWatcher and set its properties.
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = _ParameterFileDirrectory;
/* Watch for changes in LastAccess and LastWrite times, and
the renaming of files or directories. */
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite
| NotifyFilters.FileName | NotifyFilters.DirectoryName;
// Only watch .xml files.
watcher.Filter = _ParameterFileFilename; // "ParameterFile.xml";
// Add event handlers.
watcher.Changed += new FileSystemEventHandler(OnChanged);
watcher.Created += new FileSystemEventHandler(OnChanged);
watcher.Deleted += new FileSystemEventHandler(OnChanged);
watcher.Renamed += new RenamedEventHandler(OnRenamed);
// Begin watching.
watcher.EnableRaisingEvents = true;
}
catch(Exception ex)
{
Console.WriteLine(ex.Message);
}
}
// Define the event handlers.
private static void OnChanged(object source, FileSystemEventArgs e)
{
// Specify what is done when a file is "Changed", "Created", or "Deleted".
Console.WriteLine("File: " + e.FullPath + " " + e.ChangeType);
if (e.ChangeType.ToString() == "Changed")
{
ReadXmlParameters(); //Read the Parameters from XML again
MyThreadClass1._waitTillParametersChange.Set(); //Notifying the thread that the parameters might have chnaged
}
}
}
The above implementation is working fine for me. I have to start the FileWatcher from the Main() using the following lines:
public static void Main()
{
ReadXml.ReadXmlParameters();
ReadXml.CreateXMLWatcher();
// Start other threads now
}
and then I start my other threads.
QUESTION: Since with the above-mentioned implementation, I have got Static methods and variables in my program so, I am wondering if this is the proper (at least acceptable) implementation of a FileWatcher or should I try to get rid of these static things by implementing ReadXml as a singleton class (or providing the same object to all the thread classes).
Is there an Event I can capture for when a known file has been closed by an external application?
For example, a user is editing a workbook in Excel and I want to read that file as soon as the user finishes working on it and closes the file.
My current solution is to use a combination of FileSystemWatcher and Timer. The FileSystemWatcher will detect when changes have been made to a file, and start a new thread running a Timer to check when the file has closed (via try-catch) However I don't feel as though this is a good solution. If the user forgot to close the file and heads home for the weekend, it feels wasteful for my Timer to be running the whole time. If I increase the interval on my Timer, then my program won't be as responsive. Is there a solution that doesn't involve polling?
EDIT: updated with code example of what I have
private System.Windows.Forms.Timer processTimer;
private string blockedFile;
// Starts here. File changes were detected.
private void OnFileSystemWatcher_Changed(object source, FileSystemEventArgs e)
{
FileSystemWatcher fsw = (FileSystemWatcher)source;
string fullpath = Path.Combine(fsw.Path, fsw.Filter);
StartFileProcessing(fullpath);
}
private void StartFileProcessing(string filePath)
{
if (isFileOpen(new FileInfo(filePath)))
{
blockedFile = filePath;
processTimer = new System.Windows.Forms.Timer();
processTimer.Interval = 1000; // 1 sec
processTimer.Tick += new EventHandler(processTimer_Elapsed);
processTimer.Enabled = true;
processTimer.Start();
}
else
ProcessFile(filePath);
}
private void ProcessFile(string filePath)
{
// Do stuff, read + writes to the file.
}
// GOAL: Without polling, how can I get rid of this step just know right away when the file has been closed?
private void processTimer_Elapsed(object sender, EventArgs e)
{
if (isFileOpen(new FileInfo(blockedFile)) == false)
{
// The file has been freed up
processTimer.Enabled = false;
processTimer.Stop();
processTimer.Dispose();
ProcessFile(blockedFile);
}
}
// Returns true if the file is opened
public bool isFileOpen(FileInfo file)
{
FileStream str = null;
try
{
str = file.Open(FileMode.Open, FileAccess.Read, FileShare.None);
}
catch (IOException)
{
return true;
}
finally
{
if (str != null)
str.Close();
}
return false;
}
My task is to delete file once the processing is completed . I am using FileWatcher to complete this task. It is watching specific folder . Suppose If i copy one file and put that in filewatcher folder it is deleting. Second time when i copy the same file and paste that in the same watching folder. This time it says that Another process is using that file . and exception is throwing . I think i am missing something. Here is my code
private static void Main(string[] args)
{
var fw = new FileSystemWatcher(EmailSetting.DataFolder)
{
IncludeSubdirectories = false
,
EnableRaisingEvents = true
};
fw.Created += (sender, e) =>
{
File.Delete(e.FullPath);
};
Console.ReadLine();
}
You receive the Created event when the file was created (hence the name). But at this point in time the other process that is actually creating it, didn't finish writing content into that file. So the file might be already there, but the other is still working on it (imagine you would copy a 8 GB file).
It would be wiser to simply write the path of the file into a list within the event and let another thread regularly check this concurrent bag (e.g. once a second). First it checks if the file exists and if yes, try to delete it. If succeeded, remove it from the bag, otherwise try again next time.
Code example
private static readonly ConcurrentQueue<FileInfo> _FileCandidates = new ConcurrentQueue<FileInfo>();
private static void Main(string[] args)
{
var watcher = new FileSystemWatcher
{
Path = #"R:\TestFolder",
IncludeSubdirectories = false,
Filter = "*.*",
};
Console.WriteLine("Start watching folder... " + watcher.Path);
watcher.Created += OnFileCreated;
watcher.EnableRaisingEvents = true;
var timer = new Timer
{
AutoReset = true,
Interval = 1000,
};
timer.Elapsed += OnTimerElapsed;
timer.Enabled = true;
Console.ReadKey();
}
static void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
FileInfo file;
var stillInUseFiles = new List<FileInfo>();
Console.WriteLine("Check for file candidates...");
while (_FileCandidates.TryDequeue(out file))
{
try
{
Console.WriteLine("Delete " + file.FullName);
if (file.Exists)
file.Delete();
}
catch (IOException)
{
Console.WriteLine("Could not delete file, try again next time.");
stillInUseFiles.Add(file);
}
}
foreach (var unhappyFile in stillInUseFiles)
{
_FileCandidates.Enqueue(unhappyFile);
}
}
static void OnFileCreated(object sender, FileSystemEventArgs e)
{
Console.WriteLine("Found new file candidate " + e.FullPath);
_FileCandidates.Enqueue(new FileInfo(e.FullPath));
}
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 have a filesystemwatcher that will trigger an event when a file is modified. I want to read from that file once the lock has been removed. At the moment I am just trying to open the file once the event is triggered, when A large file is being copied the file lock stays on for a while after the events have been sent, preventing the file from being opened for read access.
Any suggestions?
This one's actually a bit of a doozie, unless the problem space has changed significantly since I last had to deal with it.
The easiest way is to simply try to open the file, catch the resulting IOException, and if the file is locked, add it to a queue to be checked later. You can't just try to process every file that comes in because there are all kinds of cases where multiple events will be generated for the same file, so setting up a retry loop on every single received event can turn into a disaster, fast. You need to queue them up instead and check the queue at a regular interval.
Here is a basic class template that should help you out with this problem:
public class FileMonitor : IDisposable
{
private const int PollInterval = 5000;
private FileSystemWatcher watcher;
private HashSet<string> filesToProcess = new HashSet<string>();
private Timer fileTimer; // System.Threading.Timer
public FileMonitor(string path)
{
if (path == null)
throw new ArgumentNullException("path");
watcher = new FileSystemWatcher();
watcher.Path = path;
watcher.NotifyFilter = NotifyFilters.FileName;
watcher.Created += new FileSystemEventHandler(FileCreated);
watcher.EnableRaisingEvents = true;
fileTimer = new Timer(new TimerCallback(ProcessFilesTimer),
null, PollInterval, Timeout.Infinite);
}
public void Dispose()
{
fileTimer.Dispose();
watcher.Dispose();
}
private void FileCreated(object source, FileSystemEventArgs e)
{
lock (filesToProcess)
{
filesToProcess.Add(e.FullPath);
}
}
private void ProcessFile(FileStream fs)
{
// Your code here...
}
private void ProcessFilesTimer(object state)
{
string[] currentFiles;
lock (filesToProcess)
{
currentFiles = filesToProcess.ToArray();
}
foreach (string fileName in currentFiles)
{
TryProcessFile(fileName);
}
fileTimer.Change(PollInterval, Timeout.Infinite);
}
private void TryProcessFile(string fileName)
{
FileStream fs = null;
try
{
FileInfo fi = new FileInfo(fileName);
fs = fi.OpenRead();
}
catch (IOException)
{
// Possibly log this error
return;
}
using (fs)
{
ProcessFile(fs);
}
lock (filesToProcess)
{
filesToProcess.Remove(fileName);
}
}
}
(Note - I'm recalling this from memory here so it might not be perfect - let me know if it's buggy.)