mirror of
https://git.lyx.org/repos/lyx.git
synced 2025-01-06 01:15:24 +00:00
Implement FileMonitor as a wrapper for QFileSystemWatcher
The new file monitor supports both boost and qt signals. It is implemented in a ressource-safe way.
This commit is contained in:
parent
22edb3df96
commit
caa54e80ee
@ -15,6 +15,9 @@
|
||||
#ifndef FILEMONITOR_H
|
||||
#define FILEMONITOR_H
|
||||
|
||||
// TODO: Remove FileMonitor
|
||||
#include "support/FileMonitor2.h"
|
||||
|
||||
#include <boost/signals2.hpp>
|
||||
|
||||
namespace lyx {
|
||||
@ -28,7 +31,8 @@ public:
|
||||
/** 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);
|
||||
|
||||
|
211
src/support/FileMonitor2.cpp
Normal file
211
src/support/FileMonitor2.cpp
Normal file
@ -0,0 +1,211 @@
|
||||
/**
|
||||
* \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"
|
192
src/support/FileMonitor2.h
Normal file
192
src/support/FileMonitor2.h
Normal file
@ -0,0 +1,192 @@
|
||||
// -*- 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
|
@ -11,6 +11,7 @@ noinst_LIBRARIES = liblyxsupport.a
|
||||
|
||||
MOCHEADER = \
|
||||
ConsoleApplicationPrivate.h \
|
||||
FileMonitor2.h \
|
||||
SystemcallPrivate.h
|
||||
|
||||
MOCEDFILES = $(MOCHEADER:%.h=moc_%.cpp)
|
||||
@ -33,6 +34,8 @@ AM_CPPFLAGS += -I$(srcdir)/.. \
|
||||
liblyxsupport_a_SOURCES = \
|
||||
FileMonitor.h \
|
||||
FileMonitor.cpp \
|
||||
FileMonitor2.h \
|
||||
FileMonitor2.cpp \
|
||||
RandomAccessList.h \
|
||||
bind.h \
|
||||
Cache.h \
|
||||
|
Loading…
Reference in New Issue
Block a user