]> git.lyx.org Git - lyx.git/blobdiff - src/support/FileMonitor.cpp
Account for old versions of Pygments
[lyx.git] / src / support / FileMonitor.cpp
index 039d9a8978b3bd09ec488077ef954d8949f7b780..c703d2bba7480df28f7dafd3b067fb7e78cdcfe3 100644 (file)
@@ -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.
  */
 #include <config.h>
 
 #include "support/FileMonitor.h"
-#include "support/FileName.h"
-#include "support/lyxlib.h"
-
-// FIXME Interface violation
-#include "frontends/Timeout.h"
 
-#include <boost/bind.hpp>
-#include <boost/filesystem/operations.hpp>
-#include <boost/signals/trackable.hpp>
+#include "support/debug.h"
+#include "support/FileName.h"
+#include "support/qstring_helpers.h"
+#include "support/unique_ptr.h"
 
+#include <QFile>
+#include <QStringList>
+#include <QTimer>
 
-using std::string;
+#include <algorithm>
 
-namespace fs = boost::filesystem;
+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();
+FileSystemWatcher & FileSystemWatcher::instance()
+{
+       // This is thread-safe because QFileSystemWatcher is thread-safe.
+       static FileSystemWatcher f;
+       return f;
+}
 
-       ///
-       FileName filename_;
 
-       ///
-       Timeout timer_;
+FileSystemWatcher::FileSystemWatcher()
+       : qwatcher_(make_unique<QFileSystemWatcher>())
+{}
 
-       /// 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_;
-};
+shared_ptr<FileMonitorGuard>
+FileSystemWatcher::getGuard(FileName const & filename)
+{
+       string const absfilename = filename.absFileName();
+       weak_ptr<FileMonitorGuard> & wptr = store_[absfilename];
+       if (shared_ptr<FileMonitorGuard> mon = wptr.lock())
+               return mon;
+       auto mon = make_shared<FileMonitorGuard>(absfilename, qwatcher_.get());
+       wptr = mon;
+       return mon;
+}
 
 
-FileMonitor::FileMonitor(FileName const & file_with_path, int interval)
-       : pimpl_(new Impl(file_with_path, interval))
-{}
+//static
+FileMonitorPtr FileSystemWatcher::monitor(FileName const & filename)
+{
+       return make_unique<FileMonitor>(instance().getGuard(filename));
+}
 
 
-FileMonitor::~FileMonitor()
-{}
+//static
+ActiveFileMonitorPtr FileSystemWatcher::activeMonitor(FileName const & filename,
+                                                      int interval)
+{
+       return make_unique<ActiveFileMonitor>(instance().getGuard(filename),
+                                             filename, interval);
+}
 
 
-void FileMonitor::reset(FileName const & file_with_path) const
+//static
+void FileSystemWatcher::debug()
 {
-       if (pimpl_->filename_ == file_with_path)
-               return;
-
-       bool const monitor = pimpl_->timer_.running();
-       if (monitor)
-               stop();
+       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);
+       }
+}
 
-       pimpl_->filename_ = file_with_path;
 
-       if (monitor)
-               start();
+FileMonitorGuard::FileMonitorGuard(string const & filename,
+                                   QFileSystemWatcher * qwatcher)
+       : filename_(filename), qwatcher_(qwatcher), exists_(true)
+{
+       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();
 }
 
 
-FileName const & FileMonitor::filename() const
+FileMonitorGuard::~FileMonitorGuard()
 {
-       return pimpl_->filename_;
+       if (!filename_.empty())
+               qwatcher_->removePath(toqstr(filename_));
 }
 
 
-void FileMonitor::start() const
+void FileMonitorGuard::refresh(bool const emit)
 {
-       if (monitoring())
-               return;
-
-       if (!fs::exists(pimpl_->filename_.toFilesystemEncoding()))
+       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_);
+               }
+       }
+}
 
-       pimpl_->timestamp_ = fs::last_write_time(pimpl_->filename_.toFilesystemEncoding());
-       pimpl_->checksum_ = sum(pimpl_->filename_);
 
-       if (pimpl_->timestamp_ && pimpl_->checksum_) {
-               pimpl_->timer_.start();
-       } else {
-               pimpl_->timestamp_ = 0;
-               pimpl_->checksum_ = 0;
+void FileMonitorGuard::notifyChange(QString const & path)
+{
+       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
+               // <https://bugreports.qt.io/browse/QTBUG-46483> (not a bug).
+               // Therefore we must refresh.
+               refresh(false);
+               Q_EMIT fileChanged(exists_);
        }
 }
 
 
-void FileMonitor::stop() const
+FileMonitor::FileMonitor(std::shared_ptr<FileMonitorGuard> monitor)
+       : monitor_(monitor)
 {
-       pimpl_->timestamp_ = 0;
-       pimpl_->checksum_ = 0;
-       pimpl_->timer_.stop();
+       connectToFileMonitorGuard();
+       refresh();
 }
 
 
-bool FileMonitor::monitoring() const
+void FileMonitor::connectToFileMonitorGuard()
 {
-       return pimpl_->timer_.running();
+       QObject::connect(monitor_.get(), SIGNAL(fileChanged(bool)),
+                        this, SLOT(changed(bool)));
 }
 
 
-unsigned long FileMonitor::checksum() const
+signals2::connection FileMonitor::connect(slot const & slot)
 {
-       // If we aren't actively monitoring the file, then recompute the
-       // checksum explicitly.
-       if (!pimpl_->timer_.running() && !pimpl_->filename_.empty())
-               return sum(pimpl_->filename_);
-
-       return pimpl_->checksum_;
+       return fileChanged_.connect(slot);
 }
 
 
-boost::signals::connection FileMonitor::connect(slot_type const & slot) const
+void FileMonitor::disconnect()
 {
-       return pimpl_->fileChanged_.connect(slot);
+       fileChanged_.disconnect_all_slots();
+       QObject::disconnect(this, SIGNAL(fileChanged(bool)));
 }
 
 
-//------------------------------
-// Implementation details follow
-//------------------------------
+void FileMonitor::changed(bool const exists)
+{
+       // emit boost signal
+       fileChanged_(exists);
+       Q_EMIT 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<FileMonitorGuard> 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 (!fs::exists(filename_.toFilesystemEncoding())) {
+       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 = fs::last_write_time(filename_.toFilesystemEncoding());
+               time_t const new_timestamp = filename_.lastModified();
 
                if (new_timestamp != timestamp_) {
                        timestamp_ = new_timestamp;
 
-                       unsigned long const new_checksum = sum(filename_);
+                       unsigned long const new_checksum = filename_.checksum();
                        if (new_checksum != checksum_) {
                                checksum_ = new_checksum;
                                changed = true;
                        }
                }
        }
-
-       timer_.start();
        if (changed)
-               fileChanged_();
+               FileMonitor::changed(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"