I have a append-only log file which is monitored by a FileSystemWatcher. When the file is changed, Read() is called for the LogFile object.
The log file should be read line by line.
The goal is to read only changes i.e. lines add to the log file (skip already readed lines).
Thus the StreamReader should start to read from the position where it ended on the previous read.
My solution so far doesn't work. When I add
1
2
3
4
line by line in Notepad++ to my textfile & save each time when a line was added, the Debug output is
Initial read
1 //OK
2 //OK
3 //OK
1 //looks like the log file is read again from the beginning
2
3
4
Output should be
Initial read
1
2
3
4
Any ideas to solve this problem?
Console code
public class LogFile
{
public List<string> Contents { get; }
string _fullPath;
long position;
public LogFile(string fullPath)
{
if (File.Exists(fullPath))
{
_fullPath = fullPath;
Contents = new List<string>();
Read();
}
else
{
throw new FileNotFoundException($"{fullPath} not found");
}
}
public void Read(FileSystemWatcher fsw = null)
{
if (fsw != null)
fsw.EnableRaisingEvents = false; //set to false to prevent Changed event be fired twice
FileStream fs = null;
StreamReader sr = null;
try
{
fs = new FileStream(_fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
try
{
sr = new StreamReader(fs, Encoding.UTF8);
if (Contents.Count == 0)
{
Debug.WriteLine($"Initial read");
AddToContents(_fullPath, sr);
position = fs.Length; //store the length of the stream
}
else
{
sr.DiscardBufferedData();
sr.BaseStream.Seek(position, SeekOrigin.Begin);
AddToContents(_fullPath, sr);
position = fs.Length; //store the length of the stream
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error while reading from {_fullPath}");
//log exception
}
finally
{
if (sr != null)
sr.Close();
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error while opening {_fullPath}");
//log exception
}
finally
{
if (fs != null)
fs.Close();
if (fsw != null)
fsw.EnableRaisingEvents = true; //set raise events for the filesystemwatcher to true
}
}
private List<string> AddToContents(string fullPath, StreamReader sr)
{
List<string> newLines = new List<string>();
try
{
while (!sr.EndOfStream)
{
try
{
string line = sr.ReadLine();
if (line != string.Empty)
{
Contents.Add(line);
newLines.Add(line);
Debug.WriteLine(line);
}
}
catch (Exception ex)
{
Debug.WriteLine($"Error processing line in {fullPath}");
throw;
}
}
return newLines;
}
catch (Exception ex)
{
Debug.WriteLine($"Error while reading from {fullPath}");
throw;
}
}
}
class Program
{
static LogFile file;
static FileSystemWatcher fsw;
static void Main(string[] args)
{
string path = #"C:\Temp\test.txt";
file = new LogFile(path);
CreateWatcher(path);
Console.ReadKey();
}
private static FileSystemWatcher CreateWatcher(string fileNameFullPath)
{
fsw = new FileSystemWatcher(Path.GetDirectoryName(fileNameFullPath)); //constructor only accepts directory path
fsw.IncludeSubdirectories = false;
fsw.Filter = Path.GetFileName(fileNameFullPath); //filter for the given file
fsw.NotifyFilter = NotifyFilters.LastWrite;
fsw.EnableRaisingEvents = true;
fsw.Changed += Fsw_Changed;
return fsw;
}
private static void Fsw_Changed(object sender, FileSystemEventArgs e)
{
if (file != null)
file.Read(fsw);
}
}
Problem is
position = fs.Length; //store the length of the stream
You should store current position of the stream into position field not length of stream because sometimes FileStream.Length is zero (I don't know why)
this.position = fs.Position;
and check if FileStream.Length is zero skip that change
fs = new FileStream(this._fullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
if (fs.Length != 0)
{
try
{
sr = new StreamReader(fs);
if (this.Contents.Count == 0)
{
......
Now it's working
Related
I don't know what causes the issue, but whenever I try to read from a text file in this situation, the application freezes.
private void btnCount_Click(object sender, EventArgs e)
{
int totalVotes = 0;
int votesFirst = 0;
int votesSecond = 0;
string firstClub;
string secondClub;
FileStream fs = null;
StreamReader sr = null;
if (tb1.Text == "" || tb2.Text == "")
{
MessageBox.Show("Fill in the fields");
}
else
{
firstClub = tb1.Text;
secondClub = tb2.Text;
try
{
fs = new FileStream("C:\\Users\\dluuk\\source\\repos\\PopularFootballClubs\\Polls\\bin\\Debug\\FootballClubs.txt", FileMode.Open, FileAccess.Read);
sr = new StreamReader(fs);
string s = sr.ReadLine();
while (s != null)
{
if (s.Contains(firstClub))
{
votesFirst++;
totalVotes++;
}
else if (s.Contains(secondClub))
{
votesSecond++;
totalVotes++;
}
}
}
catch (IOException ex)
{
MessageBox.Show(ex.Message);
}
catch (Exception)
{
MessageBox.Show("something wrong happened");
}
finally
{
lblTotal.Text = totalVotes.ToString();
lbl1.Text = votesFirst.ToString();
lbl2.Text = votesSecond.ToString();
if (sr != null) { sr.Close(); }
if (fs != null) { fs.Close(); }
}
}
}
I'm closing connection and the path is correct, because I don't get any exceptions at this current state so I really can't see the problem. Maybe you can help?
You aren't updating s at all, so your loop is infinite. It's reading the first line, then looping over and over again, using the same line each time because ReadLine() is not called again in the scope of the loop (and thus you never reach the end of the file - or even line 2).
You could change to something like this:
string s;
while ((s = sr.ReadLine()) != null)
{
if (s.Contains(firstClub))
{
votesFirst++;
totalVotes++;
}
else if (s.Contains(secondClub))
{
votesSecond++;
totalVotes++;
}
}
As a side note, you should put using statements around your Streams. They are disposable and you won't need to handle it manually.
while (s != null)
You never change s in the loop, so it gets stuck in an infinite loop. Update s at the end of the loop:
while (s != null)
{
// do stuff
s = sr.ReadLine();
}
I'm trying to create a simple logging tool to monitor file changes. I have used the FileSystemWatcher to detect changes to the file, but I've discovered that the events are only triggered when the file is closed, not when the buffer is flushed. This means that if multiple lines are added before the file is closed, I will only see that when the file is closed.
here is my test example.
[TestClass]
public class FileWriteTests
{
[TestMethod]
public void TestMethodAfterClose()
{
var currentDir = Environment.CurrentDirectory;
var fileToMonitor = "test.txt";
List<string> output = new List<string>();
var watcherTest = new FileWatcherTest(fileToMonitor, currentDir, output);
File.Delete(Path.Combine(currentDir, fileToMonitor));
using (var writer = new StreamWriter(Path.Combine(currentDir, fileToMonitor), true))
{
writer.WriteLine($"test");
writer.Flush();
}
System.Threading.Thread.Sleep(10);
Assert.AreEqual(1, output.Count);
Assert.AreEqual("test", output[0]);
}
[TestMethod]
public void TestMethodAfterFlush()
{
var currentDir = Environment.CurrentDirectory;
var fileToMonitor = "test.txt";
List<string> output = new List<string>();
var watcherTest = new FileWatcherTest(fileToMonitor, currentDir, output);
File.Delete(Path.Combine(currentDir, fileToMonitor));
using (var writer = new StreamWriter(Path.Combine(currentDir, fileToMonitor), true))
{
try
{
writer.WriteLine($"test");
writer.Flush();
System.Threading.Thread.Sleep(1000);
// add break point here for BareTail
Assert.AreEqual(1, output.Count);
Assert.AreEqual("test", output[0]);
}
catch
{
Assert.Fail("Test failed");
}
}
}
public class FileWatcherTest
{
public string FileName { get; set; }
public string Directory { get; set; }
private List<string> linesRead;
private FileSystemWatcher watcher;
public FileWatcherTest(string fileName, string directory, List<string> output)
{
FileName = fileName;
Directory = directory;
linesRead = output;
watcher = new FileSystemWatcher();
watcher.Path = directory;
watcher.Filter = FileName;
watcher.Changed += Watcher_Changed;
watcher.EnableRaisingEvents = true;
watcher.NotifyFilter = NotifyFilters.Attributes |
NotifyFilters.CreationTime |
NotifyFilters.DirectoryName |
NotifyFilters.FileName |
NotifyFilters.LastAccess |
NotifyFilters.LastWrite |
NotifyFilters.Security |
NotifyFilters.Size;
}
private void Watcher_Changed(object sender, FileSystemEventArgs e)
{
using (var fileStream = File.Open(Path.Combine(Directory, FileName), FileMode.Open, FileAccess.Read, FileShare.ReadWrite | FileShare.Delete | FileShare.Inheritable))
{
using (var reader = new StreamReader(fileStream))
{
string line;
while ((line = reader.ReadLine()) != null)
{
linesRead.Add(line);
}
}
}
}
}
}
right now TestMethodAfterClose succeeds and TestMethodAfterFlush fails. When I use the program BareTail and wait at the breakpoint, I see that it updates the display before the file is closed. So that gives me an indication that it's possible. I don't know if it's possible in C# and I might need to import some native functions using dllimport. The problem is I don't know where to look
How do I make both tests succeed, while not using a timer?
EDIT:
updated the FileWatcherTest class
Unfortunately Flush doesn't flush the thing you want. I find a lot articles to explain it, for example:
https://blogs.msdn.microsoft.com/alejacma/2011/03/23/filesystemwatcher-class-does-not-fire-change-events-when-notifyfilters-size-is-used/
There is a solution since .net 4, use another overload method of FileStream: Flush(bool)
var fs = writer.BaseStream as FileStream;
fs.Flush(true);
And you only give disk 10ms to react, maybe this is another problem.
After some searching, I discovered that the FileSystemWatcher only triggers an event after a file is closed as shown in this article. The article only mentions the date modified NotifyFilter, but in my testing I found that all Notifyfilters trigger after the file is closed and never while it's still open.
For that reason, it looks like tailing a file is only possible with a looped function that continuously monitors the file for extra rows. I used the code on this link as an example.
Here is my code working:
[TestClass]
public class FileWriteTests
{
[TestMethod]
public void TestMethodAfterClose_filetailing()
{
var currentDir = Environment.CurrentDirectory;
var fileToMonitor = "test.txt";
File.Delete(Path.Combine(currentDir, fileToMonitor));
List<string> output = new List<string>();
using (var watcherTest = new PersonalFileTail(currentDir, fileToMonitor))
{
watcherTest.StartTail(delegate (string line) { output.Add(line); });
using (var writer = new StreamWriter(Path.Combine(currentDir, fileToMonitor), true))
{
writer.WriteLine($"test");
writer.Flush();
}
System.Threading.Thread.Sleep(200);
watcherTest.StopTail();
}
System.Threading.Thread.Sleep(10);
Assert.AreEqual(1, output.Count);
Assert.AreEqual("test", output[0]);
}
[TestMethod]
public void TestMethodAfterFlush_filetailing()
{
// initiate file
var currentDir = Environment.CurrentDirectory;
var fileToMonitor = "test.txt";
File.Delete(Path.Combine(currentDir, fileToMonitor));
FileInfo info = new FileInfo(Path.Combine(currentDir, fileToMonitor));
List<string> output = new List<string>();
using (var watcherTest = new PersonalFileTail(currentDir, fileToMonitor))
{
watcherTest.StartTail(delegate (string line) { output.Add(line); });
using (var writer = new StreamWriter(Path.Combine(currentDir, fileToMonitor), true))
{
try
{
writer.WriteLine($"test");
writer.Flush();
System.Threading.Thread.Sleep(1000);
Assert.AreEqual(1, output.Count);
Assert.AreEqual("test", output[0]);
}
catch
{
Assert.Fail("Test failed");
}
}
watcherTest.StopTail();
}
}
public class PersonalFileTail : IDisposable
{
private string filename;
private string directory;
private Task fileTailTask;
private Action<string> handleResults;
private volatile bool runTask;
private long lastFilePosition;
public string FileName
{
get { return Path.Combine(directory, filename); }
}
public PersonalFileTail(string directory, string filename)
{
this.directory = directory;
this.filename = filename;
this.runTask = false;
lastFilePosition = 0;
}
public void StartTail(Action<string> handleResults)
{
this.handleResults = handleResults;
runTask = true;
fileTailTask = Task.Run(() => MonitorFileTask());
}
public void StopTail()
{
runTask = false;
fileTailTask.Wait();
}
public IEnumerable<string> ReadLinesFromFile()
{
using (StreamReader reader = new StreamReader(new FileStream(FileName,
FileMode.Open, FileAccess.Read, FileShare.ReadWrite)))
{
string line = "";
while ((line = reader.ReadLine()) != null)
{
yield return line;
}
lastFilePosition = reader.BaseStream.Length;
}
}
public void MonitorFileTask()
{
StreamReader reader = null;
FileStream stream = null;
try
{
using(stream = new FileStream(FileName, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (reader = new StreamReader(stream))
{
do
{
//if the file size has increased do something
if (reader.BaseStream.Length > lastFilePosition)
{
//seek to the last max offset
reader.BaseStream.Seek(lastFilePosition, SeekOrigin.Begin);
//read out of the file until the EOF
string line = "";
while ((line = reader.ReadLine()) != null)
{
handleResults(line);
}
//update the last max offset
lastFilePosition = reader.BaseStream.Position;
}
// sleep task for 100 ms
System.Threading.Thread.Sleep(100);
}
while (runTask);
}
}
catch
{
if (reader != null)
reader.Dispose();
if (stream != null)
stream.Dispose();
}
}
public void Dispose()
{
if(runTask)
{
runTask = false;
fileTailTask.Wait();
}
}
}
}
If anyone knows a way in which tailing can be done without using a timed function, I will accept that as the answer. Until that time, i feel that my answer is the only possible way to do this.
I am using this code to monitor creation of files in certain folder:
_watcher = new RecoveringFileSystemWatcher(SourceFolder, "*.xml");
_watcher.Created += (_, e) =>
{
ProcessFile(e.Name);
};
RecoveringFileSystemWatcher is a fileSystemWatcher wrapper. It's constructor is:
public RecoveringFileSystemWatcher (string path, string filter)
{
_containedFSW = new FileSystemWatcher(path, filter);
}
The process works as expected but for some files, randomly, an exception is thrown telling that the file is used by another process.
This is the method that is launched upon file creation:
var nfo = new FileInfo(filePath);
if (nfo.Exists)
{
var archivoXml = nfo.Name;
string archivo = String.Empty;
try
{
string content = Task.Run(async () => await GetFileContent(filePath)).Result;
if (String.IsNullOrEmpty(content))
return false;
XmlDocument xml = new XmlDocument();
xml.LoadXml(content);
//The rest of the method
}
}
the method GetFileContent is this:
private async Task<string> GetFileContent(string filePath)
{
string content = String.Empty;
try
{
Console.Write("ONE - "); InfoLog.Save($"ONE {filePath}");
using (StreamReader sr = new StreamReader(filePath))
{
Console.Write("TWO - "); InfoLog.Save($"TWO {filePath}");
content = await sr.ReadToEndAsync().ConfigureAwait(false);
Console.Write($"THREE {(sr.BaseStream == null ? "Closed" : "Opened")} - "); InfoLog.Save($"THREE {(sr.BaseStream == null ? "Closed" : "Opened")} {filePath}");
sr.Close();
Console.WriteLine($"FOUR {(sr.BaseStream == null ? "Closed" : "Opened")}"); InfoLog.Save($"FOUR {(sr.BaseStream == null ? "Closed" : "Opened")} {filePath}");
}
}
catch (Exception ex)
{
InfoLog.Save($"XML file could be read -> {filePath}. See error log.");
ErrorLog.Save(ex);
}
return content;
}
Look at the log information I am writing to debug the process.
I got one case with a file called 1112186.xml.... this is recorded in the log:
18/12/2018 19:12:10 ONE D:\GestorDocumental\Origen\1112186.xml
18/12/2018 19:12:10 XML file could not be read -> D:\GestorDocumental\Origen\1112186.xml. See error log.
As you see, the exception is thrown at the "using" instruction.
If I see the full log, I can see that file, 1112186.xml, is never used before, so the only chance is that FSW keeps the file opened. I don't know why, but it seems this is happening.
It is clear also that this process is locking the file, because when I exit the console application and then run again, the file can be processed.
Any help about this, please?
thanks
Jaime
I usually use this method to check if file is locked. I got it from one of the link in stackoverflow.
public static bool IsFileClosed(string filepath)
{
bool fileClosed = false;
int retries = 20;
const int delay = 400; // set a delay period = retries*delay milliseconds
if (!File.Exists(filepath))
return false;
do
{
try
{
// Attempts to open then close the file in RW mode, denying other users to place any locks.
FileStream fs = File.Open(filepath, FileMode.Open, FileAccess.ReadWrite, FileShare.None);
fs.Close();
fileClosed = true; // success
}
catch (IOException) { }
retries--;
if (!fileClosed)
Thread.Sleep(delay);
}
while (!fileClosed && retries > 0);
return fileClosed;
}
This is a new class called FileTimerWatcher (it will have logger injected):
public FileTimerWatcher(ILogger logger) : base(logger)
{
if (timer == null)
{
// Create a timer with a 1.5 second interval.
// monitor the files after 1.5 seconds.
timer = new Timer(delay);
// Hook up the event handler for the Elapsed event.
timer.Elapsed += new ElapsedEventHandler(ProcessFolder);
timer.AutoReset = true;
timer.Enabled = true;
}
}
private void ProcessFolder(object sender, ElapsedEventArgs e)
{
var LastChecked = DateTime.Now;
string[] files = System.IO.Directory.GetFiles(SourceDirectory, somefilter, System.IO.SearchOption.TopDirectoryOnly);
foreach (string file in files)
{
ProcessFile(file); // process file here
}
}
An audio file is saved to isolated storage but it isn't being played when clicked. i just show my code here. The code actually gets the audio file from the xap content and stores to the isolated storage. It reads back as shown but it doesn't play as expected.
method for reading back
using (var ISF = IsolatedStorageFile.GetUserStoreForApplication())
{
if (ISF.FileExists(MusicFileToPlay))
{
using (var FS = ISF.OpenFile(MusicFileToPlay, FileMode.Open, FileAccess.Read))
{
mediaelement.Stop();
mediaelement.SetSource(FS);
mediaelement.Position = System.TimeSpan.FromSeconds(0);
mediaelement.Volume = 20;
mediaelement.Play();
StatusTextBlock.Text = "Playing....";
//MediaPlayer.Play(
}
}
else
{
StatusTextBlock.Text = "No file present to play....";
}
}
method for storing into isolated storage
private void SaveSoundsToStorage()
{
foreach (string music in fileNames)
{
string filename = String.Format("Sounds/{0}.mp3", music);
SaveHelper(filename, music + ".mp3");
}
}
private void SaveHelper(string filename, string savename)
{
using (var appStorage = IsolatedStorageFile.GetUserStoreForApplication())
{
if (appStorage.FileExists(savename))
{
//MessageBox.Show("File already exists");
return;
}
StreamResourceInfo SRI = Application.GetResourceStream(new Uri(filename, UriKind.Relative));
using (IsolatedStorageFileStream FS = new IsolatedStorageFileStream(savename, FileMode.CreateNew, FileAccess.ReadWrite, FileShare.None, appStorage))
{
try
{
using (BinaryWriter BW = new BinaryWriter(FS))
{
long lg = 0;
Stream s;
try
{
if (SRI != null)
{
s = SRI.Stream;
lg = s.Length;
}
}
catch (Exception error)
{
MessageBox.Show("Stream resource exception: "+error.Message);
}
try
{
if (lg > appStorage.AvailableFreeSpace)
{
//No space available.Request more space
Int64 spaceToAdd = lg;
Int64 curAvail = appStorage.AvailableFreeSpace;
// Request more quota space.
if (!appStorage.IncreaseQuotaTo(appStorage.Quota + spaceToAdd))
{
// The user clicked NO to the
// host's prompt to approve the quota increase.
return;
}
else
{
// The user clicked YES to the
// host's prompt to approve the quota increase.
}
}
}
catch (Exception)
{
MessageBox.Show("quota increase problem.");
}
byte[] buff = new byte[32];
int count = 0;
try
{
using (BinaryReader br = new BinaryReader(SRI.Stream))
{
while (count < lg)
{
int actual = br.Read(buff, 0, buff.Length);
count += actual;
BW.Write(buff, 0, actual);
BW.Flush();
}
}
}
catch (Exception)
{
MessageBox.Show("Error at binary reader.");
}
}
}
catch (Exception)
{
MessageBox.Show("Binarywriter exception.");
}
//MessageBox.Show(filename + " saved successfully.");
StatusTextBlock.Text = savename + " saved succesfully";
}
}
}
Media element XAML code is
<MediaElement Name="mediaelement" Height="120" Width="160" AutoPlay="True" MediaFailed="mediaelement_MediaFailed_1"/>
Thanks in advance.
I've got a windows form with save/loading of XML files and it asks the user where they want to save/load it. My problem is I dont know how to change this method to load the file from where the user wants and not where the streamreader specifies.
The code below is of my button and LoadValues Method.
private void Edittask_loadbuttonClick(
object sender, EventArgs e)
{
Stream myStream = null;
var sFile1 = new OpenFileDialog();
sFile1.InitialDirectory = "c:\\";
sFile1.Filter = "xml files (*.xml)|*.xml";
sFile1.FilterIndex = 2;
sFile1.RestoreDirectory = true;
if (sFile1.ShowDialog() == DialogResult.OK)
{
try
{
if ((myStream = sFile1.OpenFile()) != null)
{
using (myStream)
{
var v = LoadValues();
this.load_task1_name.Text =
v.task1_name;
this.load_task1_desc.Text =
v.task1_desc;
this.load_task1_date.Value =
v.task1_date;
this.load_checkbox.Checked =
v.task1_checkbox;
}
}
}
catch (Exception ex)
{
MessageBox.Show(
"Error: Could not read file from disk. Original error: " +
ex.Message);
}
}
}
public Values LoadValues()
{
var serializer = new XmlSerializer(typeof (Values));
using (
TextReader textReader = new StreamReader(
"E:\\hello.xml")
)
{
return
(Values) serializer.Deserialize(textReader);
}
}
I would pass the Stream from the OpenFileDialog to LoadValues(...), and use that to construct your StreamReader:
public Values LoadValues(Stream stream)
{
XmlSerializer serializer = new XmlSerializer(typeof(Values));
using (TextReader textReader = new StreamReader(stream))
{
return (Values)serializer.Deserialize(textReader);
}
}
and
if ((myStream = sFile1.OpenFile()) != null)
{
using (myStream)
{
Values v = LoadValues(myStream);
...
}
}
You need to pass the Stream as a parameter to your function.