i have written a service in C# which should move backup files (*.bak and *.trn) from a database server to a special backup server. This works quite well so far. The problem is that it tries to move single files twice. This fails of course. I have configured FileSystemWatcher as follows:
try
{
m_objWatcher = new FileSystemWatcher();
m_objWatcher.Filter = m_strFilter;
m_objWatcher.Path = m_strSourcepath.Substring(0, m_strSourcepath.Length - 1);
m_objWatcher.IncludeSubdirectories = m_bolIncludeSubdirectories;
m_objWatcher.NotifyFilter = NotifyFilters.LastWrite | NotifyFilters.LastAccess; // | NotifyFilters.CreationTime;
m_objWatcher.Changed += new FileSystemEventHandler(objWatcher_OnCreated);
}
catch (Exception ex)
{
m_objLogger.d(TAG, m_strWatchername + "InitFileWatcher(): " + ex.ToString());
}
Is it possible that the Watcher produces an event twice for the same file? If I set the filter to CreationTime only, it does not react at all.
How do I have to set the Watcher to fire an event only once per file?
Thanks in advance for your help
The documentation states that common file system operations might raise more than one event. Check under the Events and Buffer Sizes heading.
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.
It also offers a few guidelines, including:
Keep your event handling code as short as possible.
To that end, you could use your FileSystemWatcher.Changed event to queue files for processing, then process them later. This is a quick example of what that might look like using an instance of System.Threading.Timer to process the queue.
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
public class ServiceClass
{
public ServiceClass()
{
_processing = false;
_fileQueue = new ConcurrentQueue<string>();
_timer = new System.Threading.Timer(ProcessQueue);
// Schedule the time to run in 5 seconds, then again every 5 seconds.
_timer.Change(5000, 5000);
}
private void objWatcher_OnChanged(object sender, FileSystemEventArgs e)
{
// Just queue the file to be processed later. If the same file is added multiple
// times, we'll skip the duplicates when processing the files.
_fileQueue.Enqueue(e.FilePath);
}
private void ProcessQueue(object state)
{
if (_processing)
{
return;
}
_processing = true;
var failures = new HashSet<string>();
try
{
while (_fileQueue.TryDequeue(out string fileToProcess))
{
if (!File.Exists(fileToProcess))
{
// Probably a file that was added multiple times and it was
// already processed.
continue;
}
var file = new FileInfo(fileToProcess);
if (FileIsLocked(file))
{
// File is locked. Maybe you got the Changed event, but the file
// wasn't done being written.
failures.Add(fileToProcess);
continue;
}
try
{
fileInfo.MoveTo(/*Your destination*/);
}
catch (Exception)
{
// File failed to move. Add it to the failures so it can be tried
// again.
failutes.Add(fileToProcess);
}
}
}
finally
{
// Add any failures back to the queue to try again.
foreach (var failedFile in failures)
{
_fileQueue.Enqueue(failedFile);
}
_processing = false;
}
}
private bool IsFileLocked(FileInfo file)
{
try
{
using (FileStream stream = file.Open(FileMode.Open, FileAccess.Read,
FileShare.None))
{
stream.Close();
}
}
catch (IOException)
{
return true;
}
return false;
}
private System.Threading.Timer _timer;
private bool _processing;
private ConcurrentQueue<string> _fileQueue;
}
Credit where it's due, I took FileIsLocked from this answer.
Some other things you might need to consider:
What happens if your FileSystemWatcher misses an event? [The documentation] does state that it is possible.
Note that a FileSystemWatcher may miss an event when the buffer size is exceeded. To avoid missing events, follow these guidelines:
Increase the buffer size by setting the InternalBufferSize property.
Avoid watching files with long file names, because a long file name contributes to filling up the buffer. Consider renaming these files using shorter names.
Keep your event handling code as short as possible.
What happens if your service crashes, but the process writing backup files continues to write them? When you restart your service, will it pick those files up and move them?
I tried all sorts of ideas to stop this. The events are too close together... it can't be stopped in the FileChanged Event. Here is my working solution:
private System.Timers.Timer timer;
private FileSystemWatcher fwatcher;
static void Main(string[] args)
{
new Program();
}
private Program()
{
timer = new System.Timers.Timer(100);
timer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
timer.AutoReset = false; // only once
fwatcher = new FileSystemWatcher();
fwatcher.Path = filePath;
fwatcher.Filter = fileName;
fwatcher.NotifyFilter = NotifyFilters.LastWrite;
fwatcher.Changed += new FileSystemEventHandler(FileChanged);
fwatcher.EnableRaisingEvents = true;
while (IsRunning)
{
Thread.Sleep(100);
}
Thread.Sleep(100);
}
private void FileChanged(object sender, FileSystemEventArgs e)
{
timer.Start();
}
private void OnTimedEvent(object source, ElapsedEventArgs e)
{
Console.WriteLine("file has changed!");
}
The timer will only fire once each time the file is changed.
Related
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#.
What I'm trying to do is watch a directory for new folders that are added.
Once a folder is added, I copy a standard directory structure from a different folder into this one and then use Open Office XML to do a Search and Replace on the contents of one of the Microsoft Word documents that is included in the source folders.
All is working great... EXCEPT:
1) If I copy more than one folder to the "watched" directory, one of them will get handled, none of the others will get processed. Also, after that, the app gets "stuck" and needs to be restarted to work again.
2) For some reason, it occasionally gets "lazy" on me and just quits working. Watching the console window it shows that it's at the WaitForChanged part of the code, but nothing happens. This seems to happen inconsistently.
Here is the FileSystemWatcher portion of the code (don't want to bore you with the OOXML and folder copying bits):
class soClass
{
private const string strDirectory = #"C:\[DIRECTORY-TO-WATCH\]";
private static FileSystemWatcher fw = new FileSystemWatcher(strDirectory);
private static void WatchIt()
{
// WHILE TRUE -- JUST MAKES IT RUN OVER AND OVER AGAIN...
while (true)
{
Console.WriteLine("Waiting on file/folder changes...");
// HANGS HERE ON THE 'REG
string strName = fw.WaitForChanged(WatcherChangeTypes.Created, -1).Name;
Console.WriteLine("File/Folder Added!");
Console.WriteLine("Starting over...");
Console.WriteLine("--------------------------------------------------------");
}
}
}
As always, any help is greatly appreciated.
UPDATE:
Here is the final code that worked, thanks to #openshac
class soClass
{
private const string strDirectory = #"C:\[DIRECTORY-TO-WATCH]";
private static void Main()
{
WatchIt();
}
public static void WatchIt()
{
FileSystemWatcher fw = new FileSystemWatcher();
fw.Path = strDirectory;
fw.Created += new FileSystemEventHandler(OnCreated);
Console.WriteLine("Waiting on file/folder changes...");
// BEGIN WATCHING
fw.EnableRaisingEvents = true;
// WAIT FOR USER TO QUIT THE PROGRAM
Console.WriteLine("Press \'q\' to quit the sample.");
while (Console.Read() != 'q') ;
}
private static void OnCreated(object sender, FileSystemEventArgs fileSystemEventArgs)
{
Console.WriteLine("File/Folder Added!");
string strName = fileSystemEventArgs.Name;
// DO STUFF HERE!!
Console.WriteLine("Starting over...");
//rwlock.ExitWriteLock();
Console.WriteLine("--------------------------------------------------------");
}
}
Also found some help here:
https://msdn.microsoft.com/en-us/library/system.io.filesystemwatcher(v=vs.110).aspx
From the documenation:
This method waits indefinitely until the first change occurs and then
returns.
https://msdn.microsoft.com/en-us/library/67220zhk(v=vs.110).aspx
If you want to listen to multiple events try using the OnCreated event:
var fw = new FileSystemWatcher();
fw.EnableRaisingEvents = true;
fw.Created += OnCreated;
}
private static void OnCreated(object sender, FileSystemEventArgs fileSystemEventArgs)
{
var name = fileSystemEventArgs.Name;
}
Actually, it has been an issue for a while where it simply stops working unfortunately. The only methods I've seen to rectify this is to handle it and restart it.
More here: FileSystemWatcher stops catching events
Good luck. Hopefully one day they fix this.
I am trying to use FileSystemWatcher to read a textfile as soon as anything gets updated into textfile in Windows Service.Now the problem that i am facing is not getting the way where i should put my FileSystemWatcher code so that i would get called as soon as textfile gets changed.Do i need to add this into OnStart() Method of Windows Service or anywhere else.
Here is my Code Structure..
protected override void OnStart(string[] args)
{
_thread = new Thread(startReadingTextFile);
_thread.Start();
}
public void startReadingTextFile() {
_freader = new AddedContentReader(TextFileLocation);
}
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
string addedContent = _freader.GetAddedLines();
}
Please help me .Thanks ..
Updated Code..
protected override void OnStart(string[] args)
{
if (lastLineReadOffset == 0)
{
_freader = new AddedContentReader(TextFileLocation);
}
//If you have saved the last position when the application did exit then you can use that value here to start from that location like the following
//_freader = new AddedContentReader("E:\\tmp\\test.txt",lastReadPosition);
else
{
_freader = new AddedContentReader(TextFileLocation, lastLineReadOffset);
}
FileSystemWatcher Watcher = new FileSystemWatcher("C:\\temp");
Watcher.EnableRaisingEvents = true;
Watcher.Changed += new FileSystemEventHandler(Watcher_Changed);
}
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
string addedContent = _freader.GetAddedLines();
//you can do whatever you want with the lines
using (StringReader reader = new StringReader(addedContent))
{
string line;
while ((line = reader.ReadLine()) != null)
{
// Call the Processing Function
}
}
}
Do i need to add this into OnStart()
Yes.
But, there is no need to create a thread for this purpose. Once FileSystemWatcher.EnableRaisingEvents is set, then events will be fired in the thread pool: you can return from OnStart.
Richard's answer is correct. However, the Changed event fires at least twice because by default FileSystemWatcher fires it once when the file is created and then again every time the file system flushes its contents to the disk. For large files you may get multiple Change events caused by multiple disk writes. If you try to open the file the first time Change fires you may get errors if the file is locked by the writing process or get an incomplete file content.
The most reliable way I have discovered is to set a timer with a short interval (a few seconds) on the very first Change event for a new file and then reset it every subsequent time the event fires for the same file. You then open the file in the timer's own Elapsed event when it does fire a few seconds after the last Change event was fired for the file.
This requires some extra code and variables:
First, create a Dictionary<string, Timer> to keep track of timers per filename.
Inside your Change event handler you need to check if the dictionary already contains the file's name as a key (inside a lock block to take care of thread concurrency issues).
If it isn't then:
create a new Timer instance
set its state object to the name of the file so when its Elapsed event fires you'll know which file you are supposed to process (the same end result can also be achieved using a closure and a lambda function but a state object is simpler)
add the new timer instance to the dictionary using the file name as the key
If it is (i.e. this is not the first Change event for that file):
look up the Timer instance in the dictionary
reset its interval to push its Elapsed event further
Then in the handler of the timer's Elapsed event you do your actual processing and cleanup:
get the file name from the timer's state object passed in the event arguments
look up the Timer instance in the dictionary by the file name and dispose of it
remove the timer from the dictionary, i.e. Remove(key) where key is the file name (the three actions above should happen inside a lock block)
open the file and do whatever you need with it.
Here's how you might want to implement this logic inside your service:
const int DELAY = 2000; // milliseconds
const WatcherChangeTypes FILE_EVENTS = WatcherChangeTypes.Created | WatcherChangeTypes.Changed | WatcherChangeTypes.Renamed;
FileSystemWatcher _fsw;
Dictionary<string, Timer> _timers = new Dictionary<string, Timer>();
object _lock = new object();
public void Start()
{
_fsw = new FileSystemWatcher(Directory, FileFilter)
{
IncludeSubdirectories = false,
EnableRaisingEvents = true
};
_fsw.Created += OnFileChanged;
_fsw.Changed += OnFileChanged;
}
private void OnFileChanged(object sender, FileSystemEventArgs e)
{
try
{
// When a file is created in the monitored directory, set a timer to process it after a short
// delay and add the timer to the queue.
if (FILE_EVENTS.HasFlag(e.ChangeType))
{
lock (_lock)
{
// File events may fire multiple times as the file is being written to the disk and/or renamed,
// therefore the first time we create a new timer and then reset it on subsequent events so that
// the file is processed shortly after the last event fires.
if (_timers.TryGetValue(e.FullPath, out Timer timer))
{
timer.Change(DELAY, 0);
}
else
{
_timers.Add(e.FullPath, new Timer(OnTimerElapsed, e.FullPath, DELAY, 0));
}
}
}
}
catch (Exception ex)
{
// handle errors
}
}
private void OnTimerElapsed(object state)
{
var fileName = (string)state;
lock (_lock)
{
try { _timers[fileName].Dispose(); } catch { }
try { _timers.Remove(fileName); } catch { }
}
// open the file ...
}
I've browsed quite a few threads on here and on other sites for solutions to this problem.
Here's my FileMonitor class:
class FileMonitor
{
public FileMonitor(String path)
{
try
{
var watcher = new FileSystemWatcher()
{
Path = path,
IncludeSubdirectories = true,
InternalBufferSize = 65536,
EnableRaisingEvents = true
};
watcher.Changed += new FileSystemEventHandler(OnFileChanged);
watcher.Created += new FileSystemEventHandler(OnFileCreated);
watcher.Deleted += new FileSystemEventHandler(OnFileDeleted);
watcher.Renamed += new RenamedEventHandler(OnFileRenamed);
watcher.Error += new ErrorEventHandler(OnWatcherError);
}
catch (Exception)
{
throw;
}
}
private void OnWatcherError(object sender, ErrorEventArgs e)
{
}
private void OnFileChanged(object sender, FileSystemEventArgs e)
{
try
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
LogFileSystemChanges(e);
}
finally
{
((FileSystemWatcher)sender).EnableRaisingEvents = true;
}
}
private void OnFileCreated(object sender, FileSystemEventArgs e)
{
try
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
LogFileSystemChanges(e);
}
finally
{
((FileSystemWatcher)sender).EnableRaisingEvents = true;
}
}
private void OnFileDeleted(object sender, FileSystemEventArgs e)
{
try
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
LogFileSystemChanges(e);
}
finally
{
((FileSystemWatcher)sender).EnableRaisingEvents = true;
}
}
private void OnFileRenamed(object sender, RenamedEventArgs e)
{
try
{
((FileSystemWatcher)sender).EnableRaisingEvents = false;
LogFileSystemRenaming(e);
}
finally
{
((FileSystemWatcher)sender).EnableRaisingEvents = true;
}
}
private void LogFileSystemChanges(FileSystemEventArgs e)
{
string log = string.Format("{0:G}: {1} | {2}", DateTime.Now, e.FullPath, e.ChangeType);
Console.WriteLine(log);
}
private void LogFileSystemRenaming(RenamedEventArgs e)
{
string log = string.Format("{0:G}: {1} | Old name: {2}", DateTime.Now, e.FullPath, e.OldName);
Console.WriteLine(log);
}
}
As you can tell, I have tried the "lock" of ((FileSystemWatcher)sender).EnableRaisingEvents = false;, but I can tell from my console output my events are triggering twice.
Any ideas on this? I'm really not sure where to go from here.
I've had the same problem and after much ado came to the conclusion that Notepad writes a file three times (!) in row. If an app really saves that much often (maybe to triangulate backups and such) there isn't much you can do.
I also noticed you have attached to all events, you should narrow it down to the least you need and filter out those you aren't interested in with the NotifyFilters.
I've tried to use the FileSystemWatcher class before (many years ago it turns out - 2008), and experienced major problems. It is a leaky abstraction at best. I reported my findings back then on CodeProject. Look for "Glytzhkof" in the comments list. As I recall I had problems with just about every aspect of the class, but it might have been improved now after so many years.
In summary my experience back in the day was that events disappeared altogether or piled up causing exceptions based on variables such as whether a disk's write cache was enabled, whether RAID, NAS or regular disks were accessed, and other random hardware issues that I do not recall exactly. Interestingly it seems it worked with UNC paths. No idea why. Mapped drives failed.
Have a look at the summary in the Code Project discussion. I am sure there are improvements since I tried it out, and a few new bugs perhaps :-). It seems disk storage hardware is hard to deal with as a high level abstraction - it leaks. Personally I ended up scanning for files manually using a service and regular disk functions. I wasted a lot of time before doing things manually.
UPDATE: See new info here: https://stackoverflow.com/a/23704476/129130
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;
}