#ifndef FILEMONITOR_H
#define FILEMONITOR_H
+// TODO: Remove FileMonitor
+#include "support/FileMonitor2.h"
+
#include <boost/signals2.hpp>
namespace lyx {
/** Once monitoring begins, the file will be monitored every
* interval ms.
*
- * FIXME: rewrite and simplify using an encapsulation of QFileSystemWatcher.
+ * This is now obsoleted by FileMonitor2 based on QFileSystemWatcher.
+ * FIXME: Remove FileMonitor
*/
FileMonitor(FileName const & file_with_path, int interval);
--- /dev/null
+/**
+ * \file FileMonitor.cpp
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Guillaume Munch
+ *
+ * Full author contact details are available in file CREDITS.
+ */
+
+#include <config.h>
+
+#include "support/FileMonitor2.h"
+
+#include "support/debug.h"
+#include "support/FileName.h"
+#include "support/qstring_helpers.h"
+#include "support/unique_ptr.h"
+
+#include <QFile>
+#include <QSignalBlocker>
+#include <QTimer>
+
+#include <algorithm>
+
+using namespace std;
+
+namespace lyx {
+namespace support {
+
+
+FileSystemWatcher & FileSystemWatcher::instance()
+{
+ // This thread-safe because QFileSystemWatcher is thread-safe.
+ static FileSystemWatcher f;
+ return f;
+}
+
+
+FileSystemWatcher::FileSystemWatcher()
+ : qwatcher_(make_unique<QFileSystemWatcher>())
+{}
+
+
+//static
+FileMonitorPtr FileSystemWatcher::monitor(FileName const & file_with_path)
+{
+ FileSystemWatcher & f = instance();
+ string const filename = file_with_path.absFileName();
+ weak_ptr<FileMonitorGuard> & wptr = f.store_[filename];
+ if (shared_ptr<FileMonitorGuard> mon = wptr.lock())
+ return make_unique<FileMonitor2>(mon);
+ auto mon = make_shared<FileMonitorGuard>(filename, f.qwatcher_.get());
+ wptr = mon;
+ return make_unique<FileMonitor2>(mon);
+}
+
+
+//static
+void FileSystemWatcher::debug()
+{
+ FileSystemWatcher & f = instance();
+ QStringList q_files = f.qwatcher_->files();
+ for (pair<string, weak_ptr<FileMonitorGuard>> pair : f.store_) {
+ string const & name = pair.first;
+ if (!pair.second.expired()) {
+ if (!q_files.contains(toqstr(name)))
+ LYXERR0("Monitored but not QFileSystemWatched (bad): " << name);
+ else {
+ //LYXERR0("Monitored and QFileSystemWatched (good): " << name);
+ }
+ }
+ }
+ for (QString const & qname : q_files) {
+ string const name = fromqstr(qname);
+ weak_ptr<FileMonitorGuard> & wptr = f.store_[name];
+ if (wptr.expired())
+ LYXERR0("QFileSystemWatched but not monitored (bad): " << name);
+ }
+}
+
+
+FileMonitorGuard::FileMonitorGuard(string const & filename,
+ QFileSystemWatcher * qwatcher)
+ : filename_(filename), qwatcher_(qwatcher)
+{
+ QObject::connect(qwatcher, SIGNAL(fileChanged(QString const &)),
+ this, SLOT(notifyChange(QString const &)));
+ if (qwatcher_->files().contains(toqstr(filename)))
+ LYXERR0("This file is already being QFileSystemWatched: " << filename
+ << ". This should not happen.");
+ refresh();
+}
+
+
+FileMonitorGuard::~FileMonitorGuard()
+{
+ qwatcher_->removePath(toqstr(filename_));
+}
+
+
+void FileMonitorGuard::refresh(bool new_file)
+{
+ QString const qfilename = toqstr(filename_);
+ if(!qwatcher_->files().contains(qfilename)) {
+ bool exists = QFile(qfilename).exists();
+ if (!exists || !qwatcher_->addPath(qfilename)) {
+ if (exists)
+ LYXERR(Debug::FILES,
+ "Could not add path to QFileSystemWatcher: "
+ << filename_);
+ QTimer::singleShot(1000, this, [=](){
+ refresh(new_file || !exists);
+ });
+ } else if (exists && new_file)
+ Q_EMIT fileChanged();
+ }
+}
+
+
+void FileMonitorGuard::notifyChange(QString const & path)
+{
+ if (path == toqstr(filename_)) {
+ Q_EMIT fileChanged();
+ // If the file has been modified by delete-move, we are notified of the
+ // deletion but we no longer track the file. See
+ // <https://bugreports.qt.io/browse/QTBUG-46483> (not a bug).
+ refresh();
+ }
+}
+
+
+FileMonitor2::FileMonitor2(std::shared_ptr<FileMonitorGuard> monitor)
+ : monitor_(monitor)
+{
+ connectToFileMonitorGuard();
+ refresh();
+}
+
+
+void FileMonitor2::connectToFileMonitorGuard()
+{
+ QObject::connect(monitor_.get(), SIGNAL(fileChanged()),
+ this, SLOT(changed()));
+}
+
+
+boost::signals2::connection
+FileMonitor2::connect(sig::slot_type const & slot)
+{
+ return fileChanged_.connect(slot);
+}
+
+
+void FileMonitor2::disconnect()
+{
+ fileChanged_.disconnect_all_slots();
+ QObject::disconnect(this, SIGNAL(fileChanged()));
+}
+
+
+void FileMonitor2::changed()
+{
+ // emit boost signal
+ fileChanged_();
+ Q_EMIT fileChanged();
+}
+
+
+FileMonitorBlocker FileMonitor2::block(int delay)
+{
+ FileMonitorBlocker blocker = blocker_.lock();
+ if (!blocker)
+ blocker_ = blocker = make_shared<FileMonitorBlockerGuard>(this);
+ blocker->setDelay(delay);
+ return blocker;
+}
+
+
+FileMonitorBlockerGuard::FileMonitorBlockerGuard(FileMonitor2 * parent)
+ : QObject(parent), parent_(parent), delay_(0)
+{
+ QObject::disconnect(parent_->monitor_.get(), SIGNAL(fileChanged()),
+ parent_, SLOT(changed()));
+}
+
+
+void FileMonitorBlockerGuard::setDelay(int delay)
+{
+ delay_ = max(delay_, delay);
+}
+
+
+FileMonitorBlockerGuard::~FileMonitorBlockerGuard()
+{
+ // closures can only copy local copies
+ FileMonitor2 * parent = parent_;
+ // parent is also our QObject::parent() so we are deleted before parent.
+ // Even if delay_ is 0, the QTimer is necessary. Indeed, the notifications
+ // from QFileSystemWatcher that we meant to ignore are not going to be
+ // treated immediately, so we must yield to give us the opportunity to
+ // ignore them.
+ QTimer::singleShot(delay_, parent, [parent]() {
+ parent->connectToFileMonitorGuard();
+ });
+}
+
+} // namespace support
+} // namespace lyx
+
+#include "moc_FileMonitor2.cpp"
--- /dev/null
+// -*- C++ -*-
+/**
+ * \file FileMonitor2.h
+ * This file is part of LyX, the document processor.
+ * Licence details can be found in the file COPYING.
+ *
+ * \author Guillaume Munch
+ *
+ * Full author contact details are available in file CREDITS.
+ *
+ * FileMonitor monitors a file and informs a listener when that file has
+ * changed.
+ */
+
+#ifndef FILEMONITOR2_H
+#define FILEMONITOR2_H
+
+#include <memory>
+
+#include <QFileSystemWatcher>
+#include <QObject>
+
+#include <boost/signals2.hpp>
+
+
+namespace lyx {
+namespace support {
+
+class FileName;
+
+///
+/// FileMonitor2, a file monitor based on QFileSystemWatcher
+///
+
+class FileMonitor2;
+class FileMonitorGuard;
+using FileMonitorPtr = std::unique_ptr<FileMonitor2>;
+
+///
+/// Watch a file:
+/// FileMonitorPtr monitor = FileSystemWatcher::monitor(file_with_path);
+/// monitor.connect(...); //(using boost::signals2), or:
+/// connect(monitor, SIGNAL(fileChanged()),...); // (using Qt)
+///
+/// Remember that a unique_ptr is automatically deleted at the end of a scope if
+/// it has not been moved, or when assigned. When that happens, the signal
+/// object is deleted and therefore all the connections are closed. The file
+/// ceases being tracked when all the monitors for a file have been deleted.
+///
+/// Stop watching:
+/// * as determined statically by the scope, or
+/// * dynamically, using:
+/// monitor = nullptr;
+///
+/// Watch a different file:
+/// monitor = FileSystemWatcher::monitor(file_with_path2);
+/// monitor.connect(...);
+/// (stops watching the first)
+///
+/// Block notifications for the duration of a scope:
+/// {
+/// FileMonitorBlocker block = monitor.block();
+/// ...
+/// }
+///
+/// Reset connections:
+/// monitor.disconnect();
+/// or the disconnect method of the connection object for the boost signal.
+///
+class FileSystemWatcher
+{
+public:
+ // as described above
+ static FileMonitorPtr monitor(FileName const & file_with_path);
+ // Output whether the paths tracked by qwatcher_ and the active
+ // FileMonitorGuards are in correspondence.
+ static void debug();
+private:
+ FileSystemWatcher();
+ // A global instance is created automatically on first call to monitor
+ static FileSystemWatcher & instance();
+ // Caches the monitor guards but allow them to be destroyed
+ std::map<std::string, std::weak_ptr<FileMonitorGuard>> store_;
+ // This class is a wrapper for QFileSystemWatcher
+ std::unique_ptr<QFileSystemWatcher> const qwatcher_;
+};
+
+
+// Must be unique per path
+// Ends the watch when deleted
+class FileMonitorGuard : public QObject
+{
+ Q_OBJECT
+
+public:
+ /// Start the watch
+ FileMonitorGuard(std::string const & filename,
+ QFileSystemWatcher * qwatcher);
+ /// End the watch
+ ~FileMonitorGuard();
+ /// absolute path being tracked
+ std::string const & filename() { return filename_; }
+ /// Make sure it is being monitored, after e.g. a deletion. See
+ /// <https://bugreports.qt.io/browse/QTBUG-46483>. This is called
+ /// automatically.
+ /// \param new_file If true, emit fileChanged if the file exists and was
+ /// successfully added.
+ void refresh(bool new_file = false);
+
+Q_SIGNALS:
+ /// Connect to this to be notified when the file changes
+ void fileChanged() const;
+
+private Q_SLOTS:
+ /// Receive notifications from the QFileSystemWatcher
+ void notifyChange(QString const & path);
+
+private:
+ std::string const filename_;
+ QFileSystemWatcher * qwatcher_;
+};
+
+
+class FileMonitorBlockerGuard : public QObject
+{
+ Q_OBJECT
+ FileMonitor2 * parent_;
+ int delay_;
+
+public:
+ FileMonitorBlockerGuard(FileMonitor2 * parent);
+ ~FileMonitorBlockerGuard();
+ void setDelay(int delay);
+};
+
+
+using FileMonitorBlocker = std::shared_ptr<FileMonitorBlockerGuard>;
+
+
+/// Main class
+class FileMonitor2 : public QObject
+{
+ Q_OBJECT
+ friend class FileMonitorBlockerGuard;
+
+public:
+ FileMonitor2(std::shared_ptr<FileMonitorGuard> monitor);
+
+ using sig = boost::signals2::signal<void()>;
+ /// Connect and you'll be informed when the file has changed.
+ boost::signals2::connection connect(sig::slot_type const &);
+ /// disconnect all slots connected to the boost signal fileChanged_ or to
+ /// the qt signal fileChanged()
+ void disconnect();
+ /// absolute path being tracked
+ std::string const & filename() { return monitor_->filename(); }
+ /// Creates a guard that blocks notifications. Copyable. Notifications from
+ /// this monitor are blocked as long as there are copies around.
+ /// \param delay is the amount waited in ms after expiration of the guard
+ /// before reconnecting. This delay thing is to deal with asynchronous
+ /// notifications in a not so elegant fashion. But it can also be used to
+ /// slow down incoming events.
+ FileMonitorBlocker block(int delay = 0);
+ /// Make sure the good file is being monitored, after e.g. a move or a
+ /// deletion. See <https://bugreports.qt.io/browse/QTBUG-46483>. This is
+ /// called automatically.
+ void refresh() { return monitor_->refresh(); }
+
+Q_SIGNALS:
+ /// Connect to this to be notified when the file changes
+ void fileChanged() const;
+
+private Q_SLOTS:
+ /// Receive notifications from the FileMonitorGuard
+ void changed();
+
+private:
+ void connectToFileMonitorGuard();
+ // boost signal
+ sig fileChanged_;
+ // the unique watch for our file
+ std::shared_ptr<FileMonitorGuard> const monitor_;
+ //
+ std::weak_ptr<FileMonitorBlockerGuard> blocker_;
+};
+
+
+
+} // namespace support
+} // namespace lyx
+
+#endif // FILEMONITOR2_H
MOCHEADER = \
ConsoleApplicationPrivate.h \
+ FileMonitor2.h \
SystemcallPrivate.h
MOCEDFILES = $(MOCHEADER:%.h=moc_%.cpp)
liblyxsupport_a_SOURCES = \
FileMonitor.h \
FileMonitor.cpp \
+ FileMonitor2.h \
+ FileMonitor2.cpp \
RandomAccessList.h \
bind.h \
Cache.h \