1

I'm using FileSystemWatcher to detect directory changes, and after that I read file content and insert it to database.

Here's my code:

private FileSystemWatcher _watcher;

public MainWindow()
{
    try
    {
        InitializeComponent();

        GetFiles();

        //Task.Factory.StartNew(() => GetFiles())
        //   .ContinueWith(task =>
        //   {
        //   }, System.Threading.CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.FromCurrentSynchronizationContext());
    }
    catch(Exception ex)
    {
        //..
    }
}

public bool GetFiles()
{
    _watcher = new FileSystemWatcher(Globals.iniFilesPath, "*.ini");
    _watcher.Created += FileCreated;
    _watcher.IncludeSubdirectories = false;
    _watcher.EnableRaisingEvents = true;
    return true;
}

private void FileCreated(object sender, FileSystemEventArgs e)
{
    try
    {
        string fileName = Path.GetFileNameWithoutExtension(e.FullPath);

        if (!String.IsNullOrEmpty(fileName))
        {
            string[] content = File.ReadAllLines(e.FullPath);
            string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();

            ChargingStationFile csf = new Product
            {
                Quantity = Convert.ToDecimal(newStringArray[1]),
                Amount = Convert.ToDecimal(newStringArray[2]),
                Price = Convert.ToDecimal(newStringArray[3]),
                FileName = fileName
            };

            ProductController.Instance.Save(csf);
        }
    }
    catch (Exception ex)
    {
        MessageBox.Show(ex.Message);
    }
}

If I run this code with CTRL+F5 I received this message:

enter image description here

But If I go with F5 (Debugging mode) than I receive this and not this error about cannot access process and item is sucessfully saved. This is confusing me really..

Should I dispose watcher? or something like that? Maybe I'm missing something here? enter image description here

This is first time I'm using FileSystemWatcher, obliviously something is really wrong here..

P.S I've found out that this line is causing an exception:

string[] content = File.ReadAllLines(e.FullPath);

how come?

Thanks guys

Cheers

Roxy'Pro
  • 4,216
  • 9
  • 40
  • 102

2 Answers2

2

File.ReadAllLines() cannot access the file when it is open for writing in another application but you can use a FileStream and StreamReader instead.

Replace string[] content = File.ReadAllLines(e.FullPath); with the following code and you should be able to read the contents of the file regardless of whether it is open in another application:

List<string> content = new List<string>();
using (FileStream stream = new FileStream(e.FullPath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
using (StreamReader sr = new StreamReader(stream))
{
    while (!sr.EndOfStream)
        content.Add(sr.ReadLine());
}
mm8
  • 163,881
  • 10
  • 57
  • 88
0

As mention in this answer:

Most likely what is happening here is that the FileCreated event is being raised and tries to process the file before is has been completely written to disk.

So, you need to wait until the file has finished to copy. According to this other answer:

From the documentation for FileSystemWatcher:

The OnCreated event is raised as soon as a file is created. If a file is being copied or transferred into a watched directory, the OnCreated event will be raised immediately, followed by one or more OnChanged events.

So, a workaround for your case will be to create a list of strings containing the paths of the files that could not be read in the Created method handler, and re-process those paths in the Changed event of the FileSystemWatcher (read the comments in the code) :

public partial class MainWindow : Window {
    private FileSystemWatcher _watcher;

    public MainWindow() {
        try {
            InitializeComponent();

            GetFiles();
        } catch (Exception ex) {
            MessageBox.Show($"Exception: {ex.Message}");
        }
    }

    private bool GetFiles() {
        _watcher = new FileSystemWatcher(@"C:\TestFolder", "*.ini");
        _watcher.Created += FileCreated;
        _watcher.Changed += FileChanged; // add this.
        _watcher.IncludeSubdirectories = false;
        _watcher.EnableRaisingEvents = true;
        return true;
    }

    // this field is new, and contains the paths of the files that could not be read in the Created method handler. 
    private readonly IList<string> _waitingForClose = new List<string>();

    private void FileChanged(object sender, FileSystemEventArgs e) {
        if (_waitingForClose.Contains(e.FullPath)) {
            try {
                string[] content = File.ReadAllLines(e.FullPath);
                string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();

                MessageBox.Show($"On FileChanged: {string.Join(" --- ", newStringArray)}");

                // Again, process the data from the file to saving in the database.

                // removing the path, so as not to reprocess the file..
                _waitingForClose.Remove(e.FullPath);
            } catch (Exception ex) {
                MessageBox.Show($"Exception on FileChanged: {ex.Message} - {e.FullPath}");
            }
        }
    }

    private void FileCreated(object sender, FileSystemEventArgs e) {
        try {
            string fileName = Path.GetFileNameWithoutExtension(e.FullPath);

            if (!String.IsNullOrEmpty(fileName)) {
                string[] content = File.ReadAllLines(e.FullPath);
                string[] newStringArray = content.Select(s => s.Substring(s.LastIndexOf('=') + 1)).ToArray();

                MessageBox.Show($"On FileCreated: {string.Join(" --- ", newStringArray)}");

                // process the data from the file to saving in the database.
            }
        } catch (Exception ex) {
            // if the method fails, add the path to the _waitingForClose variable
            _waitingForClose.Add(e.FullPath);
            //MessageBox.Show($"Exception on FIleCreated: {ex.Message} - {e.FullPath}");
        }
    }
}
Marlonchosky
  • 494
  • 5
  • 16
  • 1
    It could work, but if the file is a little heavy and the copy takes more than one second? are you tried @Roxy'Pro – Marlonchosky May 06 '20 at 23:34
  • 1
    Yes I've tried 5MB file, anyway biggest file I received is like 100KB so this might work hmm interesting this things about files -.- – Roxy'Pro May 06 '20 at 23:40
  • Mmm I have my doubts, but if it works for you, great! @Roxy'Pro – Marlonchosky May 06 '20 at 23:41
  • I'm receving really small ini file with 3 lins only quantity, amount, price and that's it.. it shouldn't be problem, soon I will update my questin to post what I've changed and I'll try your solution too! – Roxy'Pro May 06 '20 at 23:44
  • 1
    That's a tiny file :) for your case that will work perfectly, however consider a solution with a more general approach, for the future, in other cases of files that take longer to copy. Anyway, the way you found works, it's just adds a line of code, although it can improve @Roxy'Pro – Marlonchosky May 06 '20 at 23:49
  • @Roxy'Pro `Thread.Sleep` blocks the current thread for one second. This is not good, especially if the current thread is the UI thread. I suggest `await Task.Delay(1000)` instead. – Theodor Zoulias May 07 '20 at 08:36