X-Git-Url: https://git.lyx.org/gitweb/?a=blobdiff_plain;f=src%2Fsupport%2FFileMonitor.cpp;h=a3b17ad012f3f9a86e6505790ee16a400532b446;hb=62af7ee772f16f154225d2d0b65d77f4376b6001;hp=d9571bcc01e95210eddfe9f65b05b03c52d5934f;hpb=377ae30bd8dd025e6e676691b9142c086aad87e9;p=lyx.git diff --git a/src/support/FileMonitor.cpp b/src/support/FileMonitor.cpp index d9571bcc01..a3b17ad012 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,154 +13,222 @@ #include "support/FileMonitor.h" +#include "support/debug.h" #include "support/FileName.h" -#include "support/lyxlib.h" -#include "support/Timeout.h" +#include "support/qstring_helpers.h" +#include "support/unique_ptr.h" -#include -#include +#include +#include +#include +#include -using std::string; +using namespace std; namespace lyx { namespace support { -class FileMonitor::Impl : public boost::signals::trackable { -public: - /// - Impl(FileName const & file_with_path, int interval); - - /// - void monitorFile(); - - /// - FileName filename_; - - /// - Timeout timer_; - - /// This signal is emitted if the file is modified (has a new checksum). - FileMonitor::FileChangedSig fileChanged_; - - /** We use these to ascertain whether a file (once loaded successfully) - * has changed. - */ - time_t timestamp_; - /// - unsigned long checksum_; -}; +FileSystemWatcher & FileSystemWatcher::instance() +{ + // This is thread-safe because QFileSystemWatcher is thread-safe. + static FileSystemWatcher f; + return f; +} -FileMonitor::FileMonitor(FileName const & file_with_path, int interval) - : pimpl_(new Impl(file_with_path, interval)) +FileSystemWatcher::FileSystemWatcher() + : qwatcher_(make_unique()) {} -FileMonitor::~FileMonitor() +shared_ptr +FileSystemWatcher::getGuard(FileName const & filename) { - delete pimpl_; + 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; } -void FileMonitor::reset(FileName const & file_with_path) const +//static +FileMonitorPtr FileSystemWatcher::monitor(FileName const & filename) { - if (pimpl_->filename_ == file_with_path) - return; - - bool const monitor = pimpl_->timer_.running(); - if (monitor) - stop(); + return make_unique(instance().getGuard(filename)); +} - pimpl_->filename_ = file_with_path; - if (monitor) - start(); +//static +ActiveFileMonitorPtr FileSystemWatcher::activeMonitor(FileName const & filename, + int interval) +{ + return make_unique(instance().getGuard(filename), + filename, interval); } -FileName const & FileMonitor::filename() const +//static +void FileSystemWatcher::debug() { - return pimpl_->filename_; + 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::start() const +FileMonitorGuard::FileMonitorGuard(string const & filename, + QFileSystemWatcher * qwatcher) + : filename_(filename), qwatcher_(qwatcher), exists_(true) { - if (monitoring()) + if (filename.empty()) 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(); +} - if (!pimpl_->filename_.exists()) - return; - pimpl_->timestamp_ = pimpl_->filename_.lastModified(); - pimpl_->checksum_ = pimpl_->filename_.checksum(); +FileMonitorGuard::~FileMonitorGuard() +{ + if (!filename_.empty()) + qwatcher_->removePath(toqstr(filename_)); +} - if (pimpl_->timestamp_ && pimpl_->checksum_) { - pimpl_->timer_.start(); - } else { - pimpl_->timestamp_ = 0; - pimpl_->checksum_ = 0; + +void FileMonitorGuard::refresh(bool const emit) +{ + if (filename_.empty()) + return; + QString const qfilename = toqstr(filename_); + if (!qwatcher_->files().contains(qfilename)) { + bool const existed = exists_; + 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 + { + LYXERR(Debug::FILES, + "Could not add path to QFileSystemWatcher: " << filename_); + QTimer::singleShot(5000, this, SLOT(refresh())); + } else { + if (!exists_) + // The standard way to overwrite a file is to delete it and + // create a new file with the same name. Therefore if the file + // has just been deleted, it is smart to check not too long + // after whether it has been recreated. + QTimer::singleShot(existed ? 100 : 2000, this, SLOT(refresh())); + if (existed != exists_ && emit) + Q_EMIT fileChanged(exists_); + } } } -void FileMonitor::stop() const +void FileMonitorGuard::notifyChange(QString const & path) { - pimpl_->timestamp_ = 0; - pimpl_->checksum_ = 0; - pimpl_->timer_.stop(); + if (path == toqstr(filename_)) { + // 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). + // Therefore we must refresh. + refresh(false); + Q_EMIT fileChanged(exists_); + } } -bool FileMonitor::monitoring() const +FileMonitor::FileMonitor(std::shared_ptr monitor) + : monitor_(monitor) { - return pimpl_->timer_.running(); + connectToFileMonitorGuard(); + refresh(); } -unsigned long FileMonitor::checksum() const +void FileMonitor::connectToFileMonitorGuard() { - // If we aren't actively monitoring the file, then recompute the - // checksum explicitly. - if (!pimpl_->timer_.running() && !pimpl_->filename_.empty()) - return pimpl_->filename_.checksum(); - - return pimpl_->checksum_; + // Connections need to be asynchronous because the receiver can own this + // object and therefore is allowed to delete it. + // Qt signal: + QObject::connect(monitor_.get(), SIGNAL(fileChanged(bool)), + this, SIGNAL(fileChanged(bool)), + Qt::QueuedConnection); + // Boost signal: + QObject::connect(this, SIGNAL(fileChanged(bool)), + this, SLOT(changed(bool))); } -boost::signals::connection FileMonitor::connect(slot_type const & slot) const +signals2::connection FileMonitor::connect(slot const & slot) { - return pimpl_->fileChanged_.connect(slot); + return fileChanged_.connect(slot); } -//------------------------------ -// Implementation details follow -//------------------------------ +void FileMonitor::changed(bool const exists) +{ + // emit boost signal + fileChanged_(exists); +} -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(boost::bind(&Impl::monitorFile, this)); + QObject::connect(this, SIGNAL(fileChanged(bool)), this, SLOT(setCooldown())); + QTimer::singleShot(interval_, this, SLOT(clearCooldown())); + filename_.refresh(); + if (!filename_.exists()) + return; + timestamp_ = filename_.lastModified(); + checksum_ = filename_.checksum(); } -void FileMonitor::Impl::monitorFile() +void ActiveFileMonitor::checkModified() { - bool changed = false; + if (cooldown_) + return; - if (!filename_.exists()) { + cooldown_ = true; + bool changed = false; + filename_.refresh(); + bool exists = filename_.exists(); + if (!exists) { changed = timestamp_ || checksum_; timestamp_ = 0; checksum_ = 0; - } else { time_t const new_timestamp = filename_.lastModified(); @@ -173,11 +242,21 @@ void FileMonitor::Impl::monitorFile() } } } - - timer_.start(); if (changed) - fileChanged_(); + Q_EMIT FileMonitor::fileChanged(exists); + 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"