]> git.lyx.org Git - lyx.git/blob - src/support/FileMonitor.cpp
Provide proper fallback if a bibliography processor is not found
[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         if (filename.empty())
104                 return;
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.");
110         refresh();
111 }
112
113
114 FileMonitorGuard::~FileMonitorGuard()
115 {
116         if (!filename_.empty())
117                 qwatcher_->removePath(toqstr(filename_));
118 }
119
120
121 void FileMonitorGuard::refresh(bool const emit)
122 {
123         if (filename_.empty())
124                 return;
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))
131 #else
132                 auto add_path = [&]() {
133                         qwatcher_->addPath(qfilename);
134                         return qwatcher_->files().contains(qfilename);
135                 };
136                 if (exists_ && !add_path())
137 #endif
138                 {
139                         LYXERR(Debug::FILES,
140                                "Could not add path to QFileSystemWatcher: " << filename_);
141                         QTimer::singleShot(5000, this, SLOT(refresh()));
142                 } else {
143                         if (!exists_)
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_);
151                 }
152         }
153 }
154
155
156 void FileMonitorGuard::notifyChange(QString const & path)
157 {
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.
163                 refresh(false);
164                 Q_EMIT fileChanged(exists_);
165         }
166 }
167
168
169 FileMonitor::FileMonitor(std::shared_ptr<FileMonitorGuard> monitor)
170         : monitor_(monitor)
171 {
172         connectToFileMonitorGuard();
173         refresh();
174 }
175
176
177 void FileMonitor::connectToFileMonitorGuard()
178 {
179         // Connections need to be asynchronous because the receiver can own this
180         // object and therefore is allowed to delete it.
181         // Qt signal:
182         QObject::connect(monitor_.get(), SIGNAL(fileChanged(bool)),
183                          this, SIGNAL(fileChanged(bool)),
184                          Qt::QueuedConnection);
185         // Boost signal:
186         QObject::connect(this, SIGNAL(fileChanged(bool)),
187                          this, SLOT(changed(bool)));
188 }
189
190
191 signals2::connection FileMonitor::connect(slot const & slot)
192 {
193         return fileChanged_.connect(slot);
194 }
195
196
197 void FileMonitor::changed(bool const exists)
198 {
199         // emit boost signal
200         fileChanged_(exists);
201 }
202
203
204 ActiveFileMonitor::ActiveFileMonitor(std::shared_ptr<FileMonitorGuard> monitor,
205                                      FileName const & filename, int interval)
206         : FileMonitor(monitor), filename_(filename), interval_(interval),
207           timestamp_(0), checksum_(0), cooldown_(true)
208 {
209         QObject::connect(this, SIGNAL(fileChanged(bool)), this, SLOT(setCooldown()));
210         QTimer::singleShot(interval_, this, SLOT(clearCooldown()));
211         filename_.refresh();
212         if (!filename_.exists())
213                 return;
214         timestamp_ = filename_.lastModified();
215         checksum_ = filename_.checksum();
216 }
217
218
219 void ActiveFileMonitor::checkModified()
220 {
221         if (cooldown_)
222                 return;
223
224         cooldown_ = true;
225         bool changed = false;
226         filename_.refresh();
227         bool exists = filename_.exists();
228         if (!exists) {
229                 changed = timestamp_ || checksum_;
230                 timestamp_ = 0;
231                 checksum_ = 0;
232         } else {
233                 time_t const new_timestamp = filename_.lastModified();
234
235                 if (new_timestamp != timestamp_) {
236                         timestamp_ = new_timestamp;
237
238                         unsigned long const new_checksum = filename_.checksum();
239                         if (new_checksum != checksum_) {
240                                 checksum_ = new_checksum;
241                                 changed = true;
242                         }
243                 }
244         }
245         if (changed)
246                 Q_EMIT FileMonitor::fileChanged(exists);
247         QTimer::singleShot(interval_, this, SLOT(clearCooldown()));
248 }
249
250
251 void ActiveFileMonitor::checkModifiedAsync()
252 {
253         if (!cooldown_)
254                 QTimer::singleShot(0, this, SLOT(checkModified()));
255 }
256
257
258
259 } // namespace support
260 } // namespace lyx
261
262 #include "moc_FileMonitor.cpp"