X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fsupport%2FFileMonitor.cpp;h=f1446a3aba6bde7175e769f1b80cef960b1cfadc;hb=c779e9806ef83f899756d12a01f06d3ca3aaeb7e;hp=9f959d5a3a5bac557917ab38f1bbf758cb8c1ea8;hpb=61b2bd5e7fd9399128342d9048aa814ae5f086fc;p=lyx.git diff --git a/src/support/FileMonitor.cpp b/src/support/FileMonitor.cpp index 9f959d5a3a..f1446a3aba 100644 --- a/src/support/FileMonitor.cpp +++ b/src/support/FileMonitor.cpp @@ -4,6 +4,7 @@ * Licence details can be found in the file COPYING. * * \author Angus Leeming + * \author Guillaume Munch * * Full author contact details are available in file CREDITS. */ @@ -12,152 +13,247 @@ #include "support/FileMonitor.h" +#include "support/debug.h" #include "support/FileName.h" -#include "support/Timeout.h" +#include "support/qstring_helpers.h" +#include "support/unique_ptr.h" -#include "support/bind.h" -#include +#include +#include +#include + +#include using namespace std; namespace lyx { namespace support { -class FileMonitor::Impl : public boost::signals::trackable { -public: - /// - Impl(FileName const & file_with_path, int interval); +FileSystemWatcher & FileSystemWatcher::instance() +{ + // This is thread-safe because QFileSystemWatcher is thread-safe. + static FileSystemWatcher f; + return f; +} - /// - void monitorFile(); - /// - FileName filename_; +FileSystemWatcher::FileSystemWatcher() + : qwatcher_(make_unique()) +{} - /// - Timeout timer_; - /// This signal is emitted if the file is modified (has a new checksum). - FileMonitor::FileChangedSig fileChanged_; +shared_ptr +FileSystemWatcher::getGuard(FileName const & filename) +{ + string const absfilename = filename.absFileName(); + weak_ptr & wptr = store_[absfilename]; + if (shared_ptr mon = wptr.lock()) + return mon; + auto mon = make_shared(absfilename, qwatcher_.get()); + wptr = mon; + return mon; +} - /** We use these to ascertain whether a file (once loaded successfully) - * has changed. - */ - time_t timestamp_; - /// - unsigned long checksum_; -}; +//static +FileMonitorPtr FileSystemWatcher::monitor(FileName const & filename) +{ + return make_unique(instance().getGuard(filename)); +} -FileMonitor::FileMonitor(FileName const & file_with_path, int interval) - : pimpl_(new Impl(file_with_path, interval)) -{} + +//static +ActiveFileMonitorPtr FileSystemWatcher::activeMonitor(FileName const & filename, + int interval) +{ + return make_unique(instance().getGuard(filename), + filename, interval); +} -FileMonitor::~FileMonitor() +//static +void FileSystemWatcher::debug() { - delete pimpl_; + FileSystemWatcher & f = instance(); + QStringList q_files = f.qwatcher_->files(); + for (pair> 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 & wptr = f.store_[name]; + if (wptr.expired()) + LYXERR0("QFileSystemWatched but not monitored (bad): " << name); + } } -void FileMonitor::reset(FileName const & file_with_path) const +FileMonitorGuard::FileMonitorGuard(string const & filename, + QFileSystemWatcher * qwatcher) + : filename_(filename), qwatcher_(qwatcher), exists_(true) { - if (pimpl_->filename_ == file_with_path) - return; + 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(); +} - bool const monitor = pimpl_->timer_.running(); - if (monitor) - stop(); - pimpl_->filename_ = file_with_path; +FileMonitorGuard::~FileMonitorGuard() +{ + qwatcher_->removePath(toqstr(filename_)); +} - if (monitor) - start(); + +void FileMonitorGuard::refresh() +{ + QString const qfilename = toqstr(filename_); + if(!qwatcher_->files().contains(qfilename)) { + bool exists = QFile(qfilename).exists(); +#if (QT_VERSION >= 0x050000) + if (!exists || !qwatcher_->addPath(qfilename)) +#else + auto add_path = [&]() { + qwatcher_->addPath(qfilename); + return qwatcher_->files().contains(qfilename); + }; + if (!exists || !add_path()) +#endif + { + if (exists) + LYXERR(Debug::FILES, + "Could not add path to QFileSystemWatcher: " + << filename_); + QTimer::singleShot(2000, this, SLOT(refresh())); + } else if (exists && !exists_) + Q_EMIT fileChanged(); + setExists(exists); + } } -FileName const & FileMonitor::filename() const +void FileMonitorGuard::notifyChange(QString const & path) { - return pimpl_->filename_; + 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 + // (not a bug). + refresh(); + } } -void FileMonitor::start() const +FileMonitor::FileMonitor(std::shared_ptr monitor) + : monitor_(monitor) { - if (monitoring()) - return; + QObject::connect(monitor_.get(), SIGNAL(fileChanged()), + this, SLOT(changed())); + refresh(); +} - if (!pimpl_->filename_.exists()) - return; - pimpl_->timestamp_ = pimpl_->filename_.lastModified(); - pimpl_->checksum_ = pimpl_->filename_.checksum(); +void FileMonitor::reconnectToFileMonitorGuard() +{ + monitor_->setExists(true); + QObject::connect(monitor_.get(), SIGNAL(fileChanged()), + this, SLOT(changed())); +} - if (pimpl_->timestamp_ && pimpl_->checksum_) { - pimpl_->timer_.start(); - } else { - pimpl_->timestamp_ = 0; - pimpl_->checksum_ = 0; - } + +boost::signals2::connection +FileMonitor::connect(sig::slot_type const & slot) +{ + return fileChanged_.connect(slot); } -void FileMonitor::stop() const +void FileMonitor::disconnect() { - pimpl_->timestamp_ = 0; - pimpl_->checksum_ = 0; - pimpl_->timer_.stop(); + fileChanged_.disconnect_all_slots(); + QObject::disconnect(this, SIGNAL(fileChanged())); } -bool FileMonitor::monitoring() const +void FileMonitor::changed() { - return pimpl_->timer_.running(); + // emit boost signal + fileChanged_(); + Q_EMIT fileChanged(); } -unsigned long FileMonitor::checksum() const +FileMonitorBlocker FileMonitor::block(int delay) { - // If we aren't actively monitoring the file, then recompute the - // checksum explicitly. - if (!pimpl_->timer_.running() && !pimpl_->filename_.empty()) - return pimpl_->filename_.checksum(); + FileMonitorBlocker blocker = blocker_.lock(); + if (!blocker) + blocker_ = blocker = make_shared(this); + blocker->setDelay(delay); + return blocker; +} + - return pimpl_->checksum_; +FileMonitorBlockerGuard::FileMonitorBlockerGuard(FileMonitor * monitor) + : monitor_(monitor), delay_(0) +{ + QObject::disconnect(monitor->monitor_.get(), SIGNAL(fileChanged()), + monitor, SLOT(changed())); } -boost::signals::connection FileMonitor::connect(slot_type const & slot) const +void FileMonitorBlockerGuard::setDelay(int delay) { - return pimpl_->fileChanged_.connect(slot); + delay_ = max(delay_, delay); } -//------------------------------ -// Implementation details follow -//------------------------------ +FileMonitorBlockerGuard::~FileMonitorBlockerGuard() +{ + if (!monitor_) + return; + // 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_, monitor_, SLOT(reconnectToFileMonitorGuard())); +} -FileMonitor::Impl::Impl(FileName const & file_with_path, int interval) - : filename_(file_with_path), - timer_(interval, Timeout::ONETIME), - timestamp_(0), - checksum_(0) +ActiveFileMonitor::ActiveFileMonitor(std::shared_ptr monitor, + FileName const & filename, int interval) + : FileMonitor(monitor), filename_(filename), interval_(interval), + timestamp_(0), checksum_(0), cooldown_(true) { - timer_.timeout.connect(bind(&Impl::monitorFile, this)); + QObject::connect(this, SIGNAL(fileChanged()), this, SLOT(setCooldown())); + QTimer::singleShot(interval_, this, SLOT(clearCooldown())); + if (!filename_.exists()) + return; + timestamp_ = filename_.lastModified(); + checksum_ = filename_.checksum(); } -void FileMonitor::Impl::monitorFile() +void ActiveFileMonitor::checkModified() { - bool changed = false; + if (cooldown_) + return; + cooldown_ = true; + bool changed = false; if (!filename_.exists()) { changed = timestamp_ || checksum_; timestamp_ = 0; checksum_ = 0; - } else { time_t const new_timestamp = filename_.lastModified(); @@ -171,11 +267,21 @@ void FileMonitor::Impl::monitorFile() } } } - - timer_.start(); if (changed) - fileChanged_(); + FileMonitor::changed(); + QTimer::singleShot(interval_, this, SLOT(clearCooldown())); } + +void ActiveFileMonitor::checkModifiedAsync() +{ + if (!cooldown_) + QTimer::singleShot(0, this, SLOT(checkModified())); +} + + + } // namespace support } // namespace lyx + +#include "moc_FileMonitor.cpp"