3

I have a QT app that needs to know when new data is available in a specific file. So I used QFileSystemWatcher and connected the fileChanged signal to a function that will send a message in case of changes.

The probelm is that the fileChanged signal is not emitted when another application flushes this file, but only after it closes the file.

However, the QFileSystemWatcher documentation says that this signal is emitted "when the file at the specified path is modified, renamed or removed from disk". Maybe I'm missing something; What changes are included in modified? If flushing is not included, How can I detect when new data has been written to the file?

Here is the source code:

main.cpp

#include "mainwindow.h"
#include <QApplication>

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    MainWindow w;
    w.show();

    return a.exec();
}

mainwindow.h

#include <QFileSystemWatcher>
#include <QMainWindow>

class MainWindow : public QMainWindow
{
  Q_OBJECT

public:
  explicit MainWindow(QWidget *parent = 0);
  ~MainWindow();

public slots:
  void fileChangedEvent(const QString & path);

private:
  QFileSystemWatcher * watcher;
};

mainwindow.cpp

#include "mainwindow.h"

MainWindow::MainWindow()
{
  watcher = new QFileSystemWatcher();
  connect(watcher, SIGNAL(fileChanged(QString)), this, SLOT(fileChangedEvent(QString)));
  watcher->addPath("path to file");
}

void MainWindow::fileChangedEvent(const QString & path)
{
  qDebug() << path;
}

MainWindow::~MainWindow()
{
    if(watcher!=NULL)
    {
        delete watcher;
        watcher=NULL;
    }
}

This is the code of the another application which changes the file (this is a 3rd party app, so I can't change it to be synchronized with):

#include <fstream>

int main () {

  std::ofstream outfile ("path to file");

  for (int n=0; n<100; ++n)
  {
    outfile << n;
    outfile.flush();
  }
  outfile.close();

  return 0;
}

The fileChanged() signal is emitted only after std::ofstream outfile ("path to file");and outfile.close(); are called and not after outfile.flush();

user3114639
  • 1,895
  • 16
  • 42

2 Answers2

1

On Windows, it seems that fileChanged signal is emitted when the time-stamp of the file changes, which happens when the file is closed (std::ofstream::close()) but not (at least on Windows) when it is flushed (std::ofstream::flush()). In order test it I explicitly updated the timestamp of the file each time after writing into it (after calling std::ofstream::flush()) using below function:

#include <ctime>
#include <sys/stat.h>
#ifdef _WIN32
#include <sys/utime.h>
#else
#include <utime.h>
#endif

bool UpdateFileTimestamp(std::string fileName) {
    struct stat fstat;
    struct utimbuf new_time;

    if (0 != stat(fileName.c_str(), &fstat))
        return false;

    new_time.actime = fstat.st_atime;
    new_time.modtime = time(NULL);
    if (0 != utime(fileName.c_str(), &new_time))
        return false;

    return true;
}

And it worked. fileChanged signal was emitted as expected.

Sajal
  • 1,783
  • 1
  • 17
  • 21
0

That loop of write and flush is pretty fast (microseconds!?). You can not expect that the QFileSytemWatcher get's notice of all of that actions as it may well be implemented using timers .. and everything is over so fast that you are informed only unreliably about what's going on.

I just testet it and found out this assumption is correct. Consider the following code. It is writer which writes either timed or everything in a row without delay. Using a delay will give your watcher time to get notice about every single flush. But without delay it rarely reports more than two file system changes.

#include "mainwindow.h"

#include <QDebug>
#include <QTimer>

MainWindow::MainWindow()
    : m_count(10), m_file("./file")
{

  m_file.open(QFile::Truncate | QFile::ReadWrite);

// CHECK THIS:

  QTimer * t = new QTimer(this);
  connect(t,SIGNAL(timeout()), SLOT(writeChunk()));
  t->start(100);

// AGAINST THIS:

  //for(int i = 0; i < 10; i++) writeChunk();
}

void MainWindow::writeChunk()
{
  qDebug() << "wrinteChunk()";
  m_file.write("Hallo Spencer!");
  m_file.flush();

  if( ! --m_count ) {
      m_file.close();
      qDebug() << "Wrote 10 chunks";
      exit(0);
  }
}

MainWindow::~MainWindow()
{

}
Aaron
  • 1,181
  • 7
  • 15
  • Do you mean that I need to change the write\flash loop? As I wrote- this is a 3rd party app, so I can't change it. Also, I've tried to add 'Sleep(1000);' after the flush() but the watcher still doesn't detect the flushes, although it has time to get notice about every single flush... – user3114639 Dec 15 '15 at 15:10
  • This works with Linux .. 100ms and below are no problem. What is your operation system ? – Aaron Dec 15 '15 at 15:11
  • If you can not change it and it doesn't sleep anyway you won't have a chance anyway : ( – Aaron Dec 15 '15 at 15:12
  • I'm working on windows 8.1. You're right- I need a solution that doesn't involve any external change. I can check the file content every x seconds, but then I may have unnecessary checks, or delays in receiving new data. – user3114639 Dec 15 '15 at 17:05
  • Do you know whether or not the 3rd party software actually uses flush() and does it only open the file once, writes from time to time (every few seconds or milliseconds) and closes only on exit lets say an hour later ? Just to get the right picture .. and is performance critical. – Aaron Dec 15 '15 at 17:13
  • If it not really flushes or flushes in a weird way, as Windows does a lot of things, polling may simply not work because the changes may not become visible until the file is closed. – Aaron Dec 15 '15 at 17:23