I create a FileSystemWatcher object to monitor a log file. It's initialized to listen to all possible events (lastwrite, lastaccess, etc..) but it doesn't fire events when the file is being written to.
however, if I open SMSTrace and listen to that file with it (and clearly see that the file is updated constantly) the filesystemwatcher DOES fire events.
What does SMSTrace do to the file?
how can this be explained and how can I fix it?
This is the code:
private FileSystemWatcher fileWatcher;
private FileStream fileStream;
private StreamReader streamReader;
private String fullPath = null;
private String dir = null;
private String fileName = null;
private void selectLogFile()
{
// TODO: try to restore previous settings before asking the user for the log file
OpenFileDialog openFileDialog = new OpenFileDialog();
openFileDialog.InitialDirectory = "C:\\";
openFileDialog.Filter = "txt files (*.txt)|*.txt|All files (*.*)|*.*";
openFileDialog.FilterIndex = 2;
openFileDialog.RestoreDirectory = true;
if (openFileDialog.ShowDialog() == DialogResult.OK)
{
fullPath = openFileDialog.FileName;
dir = Path.GetDirectoryName(fullPath);
fileName = Path.GetFileName(fullPath);
}
}
// TODO: what to do when file erases? (reboot) - clear the window?
public LogListener()
{
try
{
Thread selectFileThread = new Thread(new ThreadStart(selectLogFile));
selectFileThread.SetApartmentState(ApartmentState.STA);
selectFileThread.Start();
selectFileThread.Join();
// The user did not select a file - nothing to do
if (fullPath == null)
{
return;
}
// Create file listener to listen on changes to log
fileWatcher = new FileSystemWatcher(dir);
// Create a file stream to read the data from the log file
fileStream = new FileStream(fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
// Create a stream reader from the fileStream to read text easily
streamReader = new StreamReader(fileStream);
// Watch for changes in LastWrite
fileWatcher.NotifyFilter = NotifyFilters.LastWrite;
// Only watch the log file.
fileWatcher.Filter = fileName;
// Add event handlers.
fileWatcher.Changed += new FileSystemEventHandler(OnChanged);
// Initial syncing of the file
readFile();
// Begin watching for events
fileWatcher.EnableRaisingEvents = true;
Log.Add("Started");
}
catch (Exception e)
{
Log.Add("Exception: " + e.Message);
}
}
void OnChanged(object source, FileSystemEventArgs e)
{
readFile();
}
public void readFile()
{
String line;
String bytesString = "";
Log.Add(DateTime.Now+":readFile()...");
// Some more code here...
}
This is a long shot, since you haven't provided any code, but...
Did you remember to set the EnableRaisingEvents property to true on your FileSystemWatcher? e.g.:
var w = new FileSystemWatcher("path");
w.Created += DoSomething;
w.Changed += DoSomething;
w.EnableRaisingEvents = true; // No events raised until you do this!
EDIT: OK, you're already doing this. Please disregard this answer, I have no idea what's going on :P
Something to look for is your code removing, then re-adding the folder you're watching after you created the flesystemwatcher. If it does that, then the watcher becomes invalid and you don't get any events raised.
I had a bug in my code doing just that today and figured it out after reading this post.
Related
StreamReader sr = new StreamReader("C:/CR EZ Test/Log.txt"); //use with IF
private void timer2_Tick(object sender, EventArgs e)
{
if ((line = sr.ReadLine()) != null)
{
//FileStream fs = File.Open("C:/CR EZ Test/Log.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
//StreamReader sr = new StreamReader(fs); //use with While can't use with }else{
//while ((line = sr.ReadLine()) != null)
//{
string[] dataLog = line.Split(new[] { ',' }, StringSplitOptions.None);
mpa = (dataLog[1]);
ml = (dataLog[2]);
lph = (dataLog[3]);
elapsedTime = float.Parse(dataLog[4]) / 1000;
if (testStatus > 0) time = elapsedTime.ToString("0.0");
tb2.Value = int.Parse(dataLog[6]);
if (chart1.Series[0].Points.Count > tb1.Value && tb1.Value > 0)
{
chart1.Series[0].Points.RemoveAt(0);
chart1.Series[1].Points.RemoveAt(0);
}
chart1.Series[0].Points.AddXY(dataLog[5], int.Parse(dataLog[1]));
chart1.Series[1].Points.AddXY(dataLog[5], int.Parse(dataLog[6]));
//}
}
else
{
sr.DiscardBufferedData();
sr.BaseStream.Seek(0, SeekOrigin.Begin);
sr.BaseStream.Position = 0;
//sr.Close();
//alertTB.Text = "";
timer2.Enabled = false;
}
alertTB.ForeColor = Color.Red;
alertTB.Text = "Data Log Viewing In Progress";
}
The issue is I am reading a text file full of variables back through a GUI, like replaying a video. As the code is shown, it works and I can control the timer tick to change the replay speed. The issue is the file is in use, so I can't write to or delete the text while the file is in use, without closing it first. I would like to either be able to find a workaround of the Streamreader, or use the Filestream to Streamreader code that will allow me to edit the file while it is in use. The issue there is, I can't figure out how to make it work with the timer, it just reads the entire file very quickly. Any help or ideas are greatly appreciated.
The issue here is how to have the commented out code to:
read a line of the text file,
have the timer to tick
then read the next line of the text file, and so on. Obviously handling the data as it arrives.
Opening a file while it is in use
I think what you are looking for is FileStream with FileShare.ReadWrite for the instance of your StreamReader (not the instance you have commented out),
var fs = new FileStream("C:\foo.txt", FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
var sr = new StreamReader(fs);
Setting the position of the stream
It also seems like based on your comments, you are having trouble with positioning the stream, this is how you could do that...
fs.Position = 0; // note this is the FileStream not the StreamReader!
// alternatively, you could use Seek
Difference between sequential and random access
Lastly, you might want to take a look below to see the difference between sequential and random access
A Potential Solution
Here is a class called FileMonitor that will check the file and update the list whenever the file is changed / updated.
I understand that you want a timer to poll the data in the text file, but in case the timer is very fast, I have optimized the FileMonitor to watch the file for changes and only extract when there is a change.
Please note that this only continues to read where it was left off, based on the position of the stream. So, it will not work if lines are deleted or modified prior to getting "extracted". This means it only functions based on your requirements and is not improved to handle a lot of other scenarios, but it should adequately cover your requirements.
public class FileMonitor : IDisposable
{
private readonly FileStream _file;
private readonly StreamReader _reader;
private long _position;
private List<string> _lines;
public FileMonitor(string file)
{
if (String.IsNullOrEmpty(nameof(file))) throw new ArgumentNullException(nameof(file));
_lines = new List<string>();
FileSystemWatcher watcher = new FileSystemWatcher();
watcher.Path = Path.GetDirectoryName(file);
watcher.Filter = Path.GetFileName(file);
watcher.NotifyFilter = NotifyFilters.LastAccess | NotifyFilters.LastWrite | NotifyFilters.FileName | NotifyFilters.DirectoryName;
watcher.Changed += new FileSystemEventHandler(OnChanged);
//watcher.Created += new FileSystemEventHandler(OnCreated);
//watcher.Deleted += new FileSystemEventHandler(OnDeleted);
//watcher.Renamed += new RenamedEventHandler(OnRenamed);
// begin watching.
watcher.EnableRaisingEvents = true;
// begin reading
_file = new FileStream(file, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
_reader = new StreamReader(_file);
_lines = ReadLines(_reader).ToList();
_position = _file.Position;
}
private void OnChanged(object source, FileSystemEventArgs e)
{
List<string> update = ReadLines(_reader).ToList();
// fix to remove the immidate newline
if (update.Count() > 0 && String.IsNullOrEmpty(update[0])) update.RemoveAt(0);
_lines.AddRange(update);
_position = _file.Position;
// just for debugging, you should remove this
Console.WriteLine($"File: {e.FullPath} [{e.ChangeType}]");
}
public IEnumerable<string> Lines { get { return _lines; } }
public void Reset()
{
_file.Position = 0;
_position = _file.Position;
_lines.Clear();
}
private static IEnumerable<string> ReadLines(StreamReader reader)
{
string line;
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
}
public void Dispose()
{
_reader.Dispose();
_file.Dispose();
}
}
Here is how you could use it with your timer
private IEnumerable<string> _lines; // holds all the lines "extracted"
void Main()
{
string file = #"C:\Data\foo.txt";
using (var timer = new System.Timers.Timer())
{
timer.Interval = 2000; // 2 second interval
timer.Elapsed += OnTimedEvent; // attach delegate
timer.Enabled = true; // start the timer
// open the file
using (var monitor = new FileMonitor(file))
{
_lines = monitor.Lines;
// loop forever, remove this
while (true) { }
}
}
}
public void OnTimedEvent(object sender, EventArgs e)
{
// just for debugging, you should remove this
Console.WriteLine($"current count: {_lines.Count()}");
}
If it isn't clear, the data extracted is held in a list of strings. Above, you can grab the "extracted" data from the monitor using the monitor.Line property.
A Proven Working Solution
string line;
if (!File.Exists(logFile))
{
viewLog.Text = "Play";
alertTB.ForeColor = Color.Red;
alertTB.Text = "File Does Not Exist | Log Data To Create File";
chart.Text = "Scope On";
}
if (File.Exists(logFile))
{
var lineCount = File.ReadLines(logFile).Count();//read text file line count to establish length for array
if (lineCount < 2)
{
viewLog.Text = "Play";
alertTB.ForeColor = Color.Red;
alertTB.Text = "File Exists | No Data Has Been Recorded";
chart.Text = "Scope On";
}
if (counter < lineCount && lineCount > 0)//if counter is less than lineCount keep reading lines
{
line = File.ReadAllLines(logFile).Skip(counter).Take(lineCount).First();
string[] dataLog = line.Split(new[] { ',' }, StringSplitOptions.None);
//-----------------------------------------Handling my data
counter++;
}
else
{
counter = 0;
timer2.Enabled = false;
}
}
This is the fix I arrived at, it allows editing the file or deleting the contents of the file. I get the line count before trying to load the file. I then use the counter to iterate through the lines. I can change the delay between the next line read based upon the timer tick interval, pause it, or stop it.
My issue is that I keep seeing a recurring theme with trying to allow my Notepad clone to save a file. Whenever I try to save a file, regardless of the location on the hard disk, the UnauthorizedAccess Exception continues to be thrown. Below is my sample code for what I've done, and I have tried researching this since last night to no avail. Any help would be greatly appreciated.
//located at base class level
private const string fileFilter = "Text Files|*.txt|All Files|*.*";
private string currentPath = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
private void MenuFileSaveAs_Click(object sender, RoutedEventArgs e)
{
SaveFileDialog sfd = new SaveFileDialog();
sfd.DefaultExt = "*.txt";
sfd.Filter = fileFilter;
sfd.AddExtension = true;
sfd.InitialDirectory = currentPath;
sfd.RestoreDirectory = true;
sfd.OverwritePrompt = true;
sfd.ShowDialog();
try
{
System.IO.File.WriteAllText(currentPath,TxtBox.Text,Encoding.UTF8);
}
catch (ArgumentException)
{
// Do nothing
}
catch(UnauthorizedAccessException)
{
MessageBox.Show("Access Denied");
}
}
Change the following lines.
...
if (sfd.ShowDialog() != true)
return;
try
{
using (var stream = sfd.OpenFile())
using (var writer = new StreamWriter(stream, Encoding.UTF8))
{
writer.Write(TxtBox.Text);
}
}
...
I hope it helps you.
You need to get the correct path context and file object from the dialog box once the user has hit 'ok'. Namely verify the user actually hit ok and then use the OpenFile property to see what their file selection is:
if (sfd.ShowDialog.HasValue && sfd.ShowDialog)
{
if (sfd.OpenFile() != null)
{
// convert your text to byte and .write()
sfd.OpenFile.Close();
}
}
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();
}
I have a WinForms application and I am looking for a way to do the following:
Click link to create a Word document from a BLOB in the database and open it.
Block the WinForms application until Word is closed.
Handle when Word is closed, check if the document was changed, and persist changes to the database.
The problem I am having is not creating the Word document and opening it, but it is hooking to the Word process to know when Word has closed. Is there some libraries to look at for doing this or any tutorials that would show how to accomplish this task?
Please see accepted solution, but here is the code I used to complete my task:
protected static string FilePath { get; set; }
public static void DisplayDocument(byte[] documentBytes, string filePath)
{
FilePath = filePath;
if (documentBytes == null) return;
if (!Directory.Exists(TEMP_FILE_DIRECTORY))
Directory.CreateDirectory(TEMP_FILE_DIRECTORY);
if (File.Exists(FilePath)) File.Delete(FilePath);
try
{
FileStream fs = new FileStream(FilePath, FileMode.Create);
fs.Write(documentBytes, 0, Convert.ToInt32(documentBytes.Length));
fs.Seek(0, SeekOrigin.Begin);
fs.Close();
ProcessStartInfo psi = new ProcessStartInfo(FilePath);
Process process = Process.Start(psi);
process.EnableRaisingEvents = true;
process.Exited += process_Exited;
process.WaitForExit();
}
catch (Exception e)
{
MessageHandler.Show(e.Message, Strings.ErrorOpeningFile);
}
}
private static void process_Exited(object sender, EventArgs e)
{
FileInfo fileInfo = new FileInfo(FilePath);
if(fileInfo.CreationTime.CompareTo(fileInfo.LastWriteTime) < 0)
Debug.WriteLine("File updated, perform database upload here.");
}
You can wait for a process to close using the following code:
process.WaitForExit();
when word will close, you can check if the file is modified and store it in database.
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.)