]> git.lyx.org Git - lyx.git/blob - src/support/FileMonitor.cpp
50337cb288db871f4c0cec6745abb39d1027c887
[lyx.git] / src / support / FileMonitor.cpp
1 /**
2  * \file FileMonitor.cpp
3  * This file is part of LyX, the document processor.
4  * Licence details can be found in the file COPYING.
5  *
6  * \author Angus Leeming
7  * \author Guillaume Munch
8  *
9  * Full author contact details are available in file CREDITS.
10  */
11
12 #include <config.h>
13
14 #include "support/FileMonitor.h"
15
16 #include "support/debug.h"
17 #include "support/FileName.h"
18 #include "support/qstring_helpers.h"
19 #include "support/unique_ptr.h"
20
21 #include <QFile>
22 #include <QStringList>
23 #include <QTimer>
24
25 #include <algorithm>
26
27 using namespace std;
28
29 namespace lyx {
30 namespace support {
31
32
33 FileSystemWatcher & FileSystemWatcher::instance()
34 {
35         // This is thread-safe because QFileSystemWatcher is thread-safe.
36         static FileSystemWatcher f;
37         return f;
38 }
39
40
41 FileSystemWatcher::FileSystemWatcher()
42         : qwatcher_(make_unique<QFileSystemWatcher>())
43 {}
44
45
46 shared_ptr<FileMonitorGuard>
47 FileSystemWatcher::getGuard(FileName const & filename)
48 {
49         string const absfilename = filename.absFileName();
50         weak_ptr<FileMonitorGuard> & wptr = store_[absfilename];
51         if (shared_ptr<FileMonitorGuard> mon = wptr.lock())
52                 return mon;
53         auto mon = make_shared<FileMonitorGuard>(absfilename, qwatcher_.get());
54         wptr = mon;
55         return mon;
56 }
57
58
59 //static
60 FileMonitorPtr FileSystemWatcher::monitor(FileName const & filename)
61 {
62         return make_unique<FileMonitor>(instance().getGuard(filename));
63 }
64
65
66 //static
67 ActiveFileMonitorPtr FileSystemWatcher::activeMonitor(FileName const & filename,
68                                                       int interval)
69 {
70         return make_unique<ActiveFileMonitor>(instance().getGuard(filename),
71                                               filename, interval);
72 }
73
74
75 //static
76 void FileSystemWatcher::debug()
77 {
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);
85                         else {
86                                 //LYXERR0("Monitored and QFileSystemWatched (good): " << name);
87                         }
88                 }
89         }
90         for (QString const & qname : q_files) {
91                 string const name = fromqstr(qname);
92                 weak_ptr<FileMonitorGuard> & wptr = f.store_[name];
93                 if (wptr.expired())
94                         LYXERR0("QFileSystemWatched but not monitored (bad): " << name);
95         }
96 }
97
98
99 FileMonitorGuard::FileMonitorGuard(string const & filename,
100                                    QFileSystemWatcher * qwatcher)
101         : filename_(filename), qwatcher_(qwatcher), exists_(true)
102 {
103         QObject::connect(qwatcher, SIGNAL(fileChanged(QString const &)),
104                          this, SLOT(notifyChange(QString const &)));
105         if (qwatcher_->files().contains(toqstr(filename)))
106                 LYXERR0("This file is already being QFileSystemWatched: " << filename
107                         << ". This should not happen.");
108         refresh();
109 }
110
111
112 FileMonitorGuard::~FileMonitorGuard()
113 {
114         qwatcher_->removePath(toqstr(filename_));
115 }
116
117
118 void FileMonitorGuard::refresh()
119 {
120         QString const qfilename = toqstr(filename_);
121         if(!qwatcher_->files().contains(qfilename)) {
122                 bool exists = QFile(qfilename).exists();
123 #if (QT_VERSION >= 0x050000)
124                 if (!exists || !qwatcher_->addPath(qfilename)) {
125 #else
126                 auto add_path = [&]() {
127                         qwatcher_->addPath(qfilename);
128                         return qwatcher_->files().contains(qfilename);
129                 };
130                 if (!exists || !add_path()) {
131 #endif
132                         if (exists)
133                                 LYXERR(Debug::FILES,
134                                        "Could not add path to QFileSystemWatcher: "
135                                        << filename_);
136                         QTimer::singleShot(2000, this, SLOT(refresh()));
137                 } else if (exists && !exists_)
138                         Q_EMIT fileChanged();
139                 setExists(exists);
140         }
141 }
142
143
144 void FileMonitorGuard::notifyChange(QString const & path)
145 {
146         if (path == toqstr(filename_)) {
147                 Q_EMIT fileChanged();
148                 // If the file has been modified by delete-move, we are notified of the
149                 // deletion but we no longer track the file. See
150                 // <https://bugreports.qt.io/browse/QTBUG-46483> (not a bug).
151                 refresh();
152         }
153 }
154
155
156 FileMonitor::FileMonitor(std::shared_ptr<FileMonitorGuard> monitor)
157         : monitor_(monitor)
158 {
159         QObject::connect(monitor_.get(), SIGNAL(fileChanged()),
160                          this, SLOT(changed()));
161         refresh();
162 }
163
164
165 void FileMonitor::reconnectToFileMonitorGuard()
166 {
167         monitor_->setExists(true);
168         QObject::connect(monitor_.get(), SIGNAL(fileChanged()),
169                          this, SLOT(changed()));
170 }
171
172
173 boost::signals2::connection
174 FileMonitor::connect(sig::slot_type const & slot)
175 {
176         return fileChanged_.connect(slot);
177 }
178
179
180 void FileMonitor::disconnect()
181 {
182         fileChanged_.disconnect_all_slots();
183         QObject::disconnect(this, SIGNAL(fileChanged()));
184 }
185
186
187 void FileMonitor::changed()
188 {
189         // emit boost signal
190         fileChanged_();
191         Q_EMIT fileChanged();
192 }
193
194
195 FileMonitorBlocker FileMonitor::block(int delay)
196 {
197         FileMonitorBlocker blocker = blocker_.lock();
198         if (!blocker)
199                 blocker_ = blocker = make_shared<FileMonitorBlockerGuard>(this);
200         blocker->setDelay(delay);
201         return blocker;
202 }
203
204
205 FileMonitorBlockerGuard::FileMonitorBlockerGuard(FileMonitor * monitor)
206         : monitor_(monitor), delay_(0)
207 {
208         QObject::disconnect(monitor->monitor_.get(), SIGNAL(fileChanged()),
209                             monitor, SLOT(changed()));
210 }
211
212
213 void FileMonitorBlockerGuard::setDelay(int delay)
214 {
215         delay_ = max(delay_, delay);
216 }
217
218
219 FileMonitorBlockerGuard::~FileMonitorBlockerGuard()
220 {
221         if (!monitor_)
222                 return;
223         // Even if delay_ is 0, the QTimer is necessary. Indeed, the notifications
224         // from QFileSystemWatcher that we meant to ignore are not going to be
225         // treated immediately, so we must yield to give us the opportunity to
226         // ignore them.
227         QTimer::singleShot(delay_, monitor_, SLOT(reconnectToFileMonitorGuard()));
228 }
229
230
231 ActiveFileMonitor::ActiveFileMonitor(std::shared_ptr<FileMonitorGuard> monitor,
232                                      FileName const & filename, int interval)
233         : FileMonitor(monitor), filename_(filename), interval_(interval),
234           timestamp_(0), checksum_(0), cooldown_(true)
235 {
236         QObject::connect(this, SIGNAL(fileChanged()), this, SLOT(setCooldown()));
237         QTimer::singleShot(interval_, this, SLOT(clearCooldown()));
238         if (!filename_.exists())
239                 return;
240         timestamp_ = filename_.lastModified();
241         checksum_ = filename_.checksum();
242 }
243
244
245 void ActiveFileMonitor::checkModified()
246 {
247         if (cooldown_)
248                 return;
249
250         cooldown_ = true;
251         bool changed = false;
252         if (!filename_.exists()) {
253                 changed = timestamp_ || checksum_;
254                 timestamp_ = 0;
255                 checksum_ = 0;
256         } else {
257                 time_t const new_timestamp = filename_.lastModified();
258
259                 if (new_timestamp != timestamp_) {
260                         timestamp_ = new_timestamp;
261
262                         unsigned long const new_checksum = filename_.checksum();
263                         if (new_checksum != checksum_) {
264                                 checksum_ = new_checksum;
265                                 changed = true;
266                         }
267                 }
268         }
269         if (changed)
270                 FileMonitor::changed();
271         QTimer::singleShot(interval_, this, SLOT(clearCooldown()));
272 }
273
274
275 void ActiveFileMonitor::checkModifiedAsync()
276 {
277         if (!cooldown_)
278                 QTimer::singleShot(0, this, SLOT(checkModified()));
279 }
280
281
282
283 } // namespace support
284 } // namespace lyx
285
286 #include "moc_FileMonitor.cpp"