* Licence details can be found in the file COPYING.
*
* \author Angus Leeming
+ * \author Guillaume Munch
*
* Full author contact details are available in file CREDITS.
*
#ifndef FILEMONITOR_H
#define FILEMONITOR_H
-#include <boost/signal.hpp>
+#include "support/FileName.h"
+
+#include <memory>
+
+#include <QFileSystemWatcher>
+#include <QObject>
+#include <QPointer>
+
+#include <boost/signals2.hpp>
+
namespace lyx {
namespace support {
-class FileName;
+///
+/// FileMonitor, a file monitor based on QFileSystemWatcher
+///
+
+class FileMonitor;
+class ActiveFileMonitor;
+class FileMonitorGuard;
+typedef std::unique_ptr<FileMonitor> FileMonitorPtr;
+typedef std::unique_ptr<ActiveFileMonitor> ActiveFileMonitorPtr;
-class FileMonitor
+///
+/// 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:
- /** Once monitoring begins, the file will be monitored every
- * interval ms.
- *
- * FIXME: rewrite and simplify using an encapsulation of QFileSystemWatcher.
- */
- FileMonitor(FileName const & file_with_path, int interval);
+ /// as described above
+ static FileMonitorPtr monitor(FileName const & filename);
+ /// same but with an ActiveFileMonitor
+ static ActiveFileMonitorPtr activeMonitor(FileName const & filename,
+ int interval = 10000);
+ /// 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
+ static FileSystemWatcher & instance();
+ ///
+ std::shared_ptr<FileMonitorGuard> getGuard(FileName const & filename);
+ /// 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_;
+};
- /// Destructor
- ~FileMonitor();
- ///
- void reset(FileName const & file_with_path) const;
+/// Must be unique per path
+/// Ends the watch when deleted
+class FileMonitorGuard : public QObject
+{
+ Q_OBJECT
- ///
- FileName const & filename() const;
+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_; }
+ /// if false, emit fileChanged() when we notice the existence of the file
+ void setExists(bool exists) { exists_ = exists; }
- /// Begin monitoring the file
- void start() const;
- ///
- void stop() const;
- ///
- bool monitoring() const;
+public Q_SLOTS:
+ /// Make sure it is being monitored, after e.g. a deletion. See
+ /// <https://bugreports.qt.io/browse/QTBUG-46483>. This is called
+ /// automatically.
+ void refresh();
+
+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_;
+ bool exists_;
+};
+
+
+class FileMonitorBlockerGuard : public QObject
+{
+ Q_OBJECT
+ QPointer<FileMonitor> monitor_;
+ int delay_;
+
+public:
+ FileMonitorBlockerGuard(FileMonitor * monitor);
+ ~FileMonitorBlockerGuard();
+ void setDelay(int delay);
+};
+
+
+typedef std::shared_ptr<FileMonitorBlockerGuard> FileMonitorBlocker;
+
+
+/// Main class
+class FileMonitor : public QObject
+{
+ Q_OBJECT
+ friend class FileMonitorBlockerGuard;
- /** The checksum is recomputed whenever the file is modified.
- * If the file is not being monitored, then the checksum will be
- * recomputed each time this function is called.
- */
- unsigned long checksum() const;
+public:
+ FileMonitor(std::shared_ptr<FileMonitorGuard> monitor);
+ typedef boost::signals2::signal<void()> sig;
/// Connect and you'll be informed when the file has changed.
- typedef boost::signal<void()> FileChangedSig;
- typedef FileChangedSig::slot_type slot_type;
+ 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;
+
+protected Q_SLOTS:
+ /// Receive notifications from the FileMonitorGuard
+ void changed();
+ ///
+ void reconnectToFileMonitorGuard();
+
+private:
+ /// boost signal
+ sig fileChanged_;
+ /// the unique watch for our file
+ std::shared_ptr<FileMonitorGuard> const monitor_;
///
- boost::signals::connection connect(slot_type const &) const;
+ std::weak_ptr<FileMonitorBlockerGuard> blocker_;
+};
+
+
+/// When a more active monitoring style is needed.
+/// For instance because QFileSystemWatcher does not work for remote file
+/// systems.
+class ActiveFileMonitor : public FileMonitor
+{
+ Q_OBJECT
+public:
+ ActiveFileMonitor(std::shared_ptr<FileMonitorGuard> monitor,
+ FileName const & filename, int interval);
+ /// call checkModified asynchronously
+ void checkModifiedAsync();
+
+public Q_SLOTS:
+ /// Check explicitly for a modification, but not more than once every
+ /// interval ms.
+ void checkModified();
+
+private Q_SLOTS:
+ void setCooldown() { cooldown_ = true; }
+ void clearCooldown() { cooldown_ = false; }
private:
- /// noncopyable
- FileMonitor(FileMonitor const &);
- void operator=(FileMonitor const &);
-
- /// Use the Pimpl idiom to hide the internals.
- class Impl;
- /// The pointer never changes although *pimpl_'s contents may.
- Impl * const pimpl_;
+ FileName const filename_;
+ ///
+ int const interval_;
+ ///
+ time_t timestamp_;
+ ///
+ unsigned long checksum_;
+ ///
+ bool cooldown_;
};
+
} // namespace support
} // namespace lyx