2 * \file FileMonitor.cpp
3 * This file is part of LyX, the document processor.
4 * Licence details can be found in the file COPYING.
6 * \author Angus Leeming
7 * \author Guillaume Munch
9 * Full author contact details are available in file CREDITS.
14 #include "support/FileMonitor.h"
16 #include "support/debug.h"
17 #include "support/FileName.h"
18 #include "support/qstring_helpers.h"
19 #include "support/unique_ptr.h"
22 #include <QStringList>
33 FileSystemWatcher & FileSystemWatcher::instance()
35 // This is thread-safe because QFileSystemWatcher is thread-safe.
36 static FileSystemWatcher f;
41 FileSystemWatcher::FileSystemWatcher()
42 : qwatcher_(make_unique<QFileSystemWatcher>())
46 shared_ptr<FileMonitorGuard>
47 FileSystemWatcher::getGuard(FileName const & filename)
49 string const absfilename = filename.absFileName();
50 weak_ptr<FileMonitorGuard> & wptr = store_[absfilename];
51 if (shared_ptr<FileMonitorGuard> mon = wptr.lock())
53 auto mon = make_shared<FileMonitorGuard>(absfilename, qwatcher_.get());
60 FileMonitorPtr FileSystemWatcher::monitor(FileName const & filename)
62 return make_unique<FileMonitor>(instance().getGuard(filename));
67 ActiveFileMonitorPtr FileSystemWatcher::activeMonitor(FileName const & filename,
70 return make_unique<ActiveFileMonitor>(instance().getGuard(filename),
76 void FileSystemWatcher::debug()
78 FileSystemWatcher & f = instance();
79 QStringList q_files = f.qwatcher_->files();
80 for (pair<string, weak_ptr<FileMonitorGuard>> pair : f.store_) {
81 string const & name = pair.first;
82 if (!pair.second.expired()) {
83 if (!q_files.contains(toqstr(name)))
84 LYXERR0("Monitored but not QFileSystemWatched (bad): " << name);
86 //LYXERR0("Monitored and QFileSystemWatched (good): " << name);
90 for (QString const & qname : q_files) {
91 string const name = fromqstr(qname);
92 weak_ptr<FileMonitorGuard> & wptr = f.store_[name];
94 LYXERR0("QFileSystemWatched but not monitored (bad): " << name);
99 FileMonitorGuard::FileMonitorGuard(string const & filename,
100 QFileSystemWatcher * qwatcher)
101 : filename_(filename), qwatcher_(qwatcher), exists_(true)
103 if (filename.empty())
105 QObject::connect(qwatcher, SIGNAL(fileChanged(QString const &)),
106 this, SLOT(notifyChange(QString const &)));
107 if (qwatcher_->files().contains(toqstr(filename)))
108 LYXERR0("This file is already being QFileSystemWatched: " << filename
109 << ". This should not happen.");
114 FileMonitorGuard::~FileMonitorGuard()
116 if (!filename_.empty())
117 qwatcher_->removePath(toqstr(filename_));
121 void FileMonitorGuard::refresh(bool const emit)
123 if (filename_.empty())
125 QString const qfilename = toqstr(filename_);
126 if (!qwatcher_->files().contains(qfilename)) {
127 bool const existed = exists_;
128 exists_ = QFile(qfilename).exists();
129 #if (QT_VERSION >= 0x050000)
130 if (exists_ && !qwatcher_->addPath(qfilename))
132 auto add_path = [&]() {
133 qwatcher_->addPath(qfilename);
134 return qwatcher_->files().contains(qfilename);
136 if (exists_ && !add_path())
140 "Could not add path to QFileSystemWatcher: " << filename_);
141 QTimer::singleShot(5000, this, SLOT(refresh()));
144 // The standard way to overwrite a file is to delete it and
145 // create a new file with the same name. Therefore if the file
146 // has just been deleted, it is smart to check not too long
147 // after whether it has been recreated.
148 QTimer::singleShot(existed ? 100 : 2000, this, SLOT(refresh()));
149 if (existed != exists_ && emit)
150 Q_EMIT fileChanged(exists_);
156 void FileMonitorGuard::notifyChange(QString const & path)
158 if (path == toqstr(filename_)) {
159 // If the file has been modified by delete-move, we are notified of the
160 // deletion but we no longer track the file. See
161 // <https://bugreports.qt.io/browse/QTBUG-46483> (not a bug).
162 // Therefore we must refresh.
164 Q_EMIT fileChanged(exists_);
169 FileMonitor::FileMonitor(std::shared_ptr<FileMonitorGuard> monitor)
172 connectToFileMonitorGuard();
177 void FileMonitor::connectToFileMonitorGuard()
179 QObject::connect(monitor_.get(), SIGNAL(fileChanged(bool)),
180 this, SLOT(changed(bool)));
184 signals2::connection FileMonitor::connect(slot const & slot)
186 return fileChanged_.connect(slot);
190 void FileMonitor::disconnect()
192 fileChanged_.disconnect_all_slots();
193 QObject::disconnect(this, SIGNAL(fileChanged(bool)));
197 void FileMonitor::changed(bool const exists)
200 fileChanged_(exists);
201 Q_EMIT fileChanged(exists);
205 FileMonitorBlocker FileMonitor::block(int delay)
207 FileMonitorBlocker blocker = blocker_.lock();
209 blocker_ = blocker = make_shared<FileMonitorBlockerGuard>(this);
210 blocker->setDelay(delay);
215 FileMonitorBlockerGuard::FileMonitorBlockerGuard(FileMonitor * monitor)
216 : monitor_(monitor), delay_(0)
218 QObject::disconnect(monitor->monitor_.get(), SIGNAL(fileChanged(bool)),
219 monitor, SLOT(changed(bool)));
223 void FileMonitorBlockerGuard::setDelay(int delay)
225 delay_ = max(delay_, delay);
229 FileMonitorBlockerGuard::~FileMonitorBlockerGuard()
233 // Even if delay_ is 0, the QTimer is necessary. Indeed, the notifications
234 // from QFileSystemWatcher that we meant to ignore are not going to be
235 // treated immediately, so we must yield to give us the opportunity to
237 QTimer::singleShot(delay_, monitor_, SLOT(connectToFileMonitorGuard()));
241 ActiveFileMonitor::ActiveFileMonitor(std::shared_ptr<FileMonitorGuard> monitor,
242 FileName const & filename, int interval)
243 : FileMonitor(monitor), filename_(filename), interval_(interval),
244 timestamp_(0), checksum_(0), cooldown_(true)
246 QObject::connect(this, SIGNAL(fileChanged(bool)), this, SLOT(setCooldown()));
247 QTimer::singleShot(interval_, this, SLOT(clearCooldown()));
249 if (!filename_.exists())
251 timestamp_ = filename_.lastModified();
252 checksum_ = filename_.checksum();
256 void ActiveFileMonitor::checkModified()
262 bool changed = false;
264 bool exists = filename_.exists();
266 changed = timestamp_ || checksum_;
270 time_t const new_timestamp = filename_.lastModified();
272 if (new_timestamp != timestamp_) {
273 timestamp_ = new_timestamp;
275 unsigned long const new_checksum = filename_.checksum();
276 if (new_checksum != checksum_) {
277 checksum_ = new_checksum;
283 FileMonitor::changed(exists);
284 QTimer::singleShot(interval_, this, SLOT(clearCooldown()));
288 void ActiveFileMonitor::checkModifiedAsync()
291 QTimer::singleShot(0, this, SLOT(checkModified()));
296 } // namespace support
299 #include "moc_FileMonitor.cpp"