]> git.lyx.org Git - lyx.git/blob - src/support/FileMonitor.cpp
a55fd2396baf240e750e71b1d00394d03136e3cd
[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 Guillaume Munch
7  *
8  * Full author contact details are available in file CREDITS.
9  */
10
11 #include <config.h>
12
13 #include "support/FileMonitor.h"
14
15 #include "support/debug.h"
16 #include "support/FileName.h"
17 #include "support/qstring_helpers.h"
18 #include "support/unique_ptr.h"
19
20 #include <QFile>
21 #include <QTimer>
22
23 #include <algorithm>
24
25 using namespace std;
26
27 namespace lyx {
28 namespace support {
29
30
31 FileSystemWatcher & FileSystemWatcher::instance()
32 {
33         // This thread-safe because QFileSystemWatcher is thread-safe.
34         static FileSystemWatcher f;
35         return f;
36 }
37
38
39 FileSystemWatcher::FileSystemWatcher()
40         : qwatcher_(make_unique<QFileSystemWatcher>())
41 {}
42
43
44 //static
45 FileMonitorPtr FileSystemWatcher::monitor(FileName const & file_with_path)
46 {
47         FileSystemWatcher & f = instance();
48         string const filename = file_with_path.absFileName();
49         weak_ptr<FileMonitorGuard> & wptr = f.store_[filename];
50         if (shared_ptr<FileMonitorGuard> mon = wptr.lock())
51                 return make_unique<FileMonitor>(mon);
52         auto mon = make_shared<FileMonitorGuard>(filename, f.qwatcher_.get());
53         wptr = mon;
54         return make_unique<FileMonitor>(mon);
55 }
56
57
58 //static
59 void FileSystemWatcher::debug()
60 {
61         FileSystemWatcher & f = instance();
62         QStringList q_files = f.qwatcher_->files();
63         for (pair<string, weak_ptr<FileMonitorGuard>> pair : f.store_) {
64                 string const & name = pair.first;
65                 if (!pair.second.expired()) {
66                         if (!q_files.contains(toqstr(name)))
67                                 LYXERR0("Monitored but not QFileSystemWatched (bad): " << name);
68                         else {
69                                 //LYXERR0("Monitored and QFileSystemWatched (good): " << name);
70                         }
71                 }
72         }
73         for (QString const & qname : q_files) {
74                 string const name = fromqstr(qname);
75                 weak_ptr<FileMonitorGuard> & wptr = f.store_[name];
76                 if (wptr.expired())
77                         LYXERR0("QFileSystemWatched but not monitored (bad): " << name);
78         }
79 }
80
81
82 FileMonitorGuard::FileMonitorGuard(string const & filename,
83                                    QFileSystemWatcher * qwatcher)
84         : filename_(filename), qwatcher_(qwatcher)
85 {
86         QObject::connect(qwatcher, SIGNAL(fileChanged(QString const &)),
87                          this, SLOT(notifyChange(QString const &)));
88         if (qwatcher_->files().contains(toqstr(filename)))
89                 LYXERR0("This file is already being QFileSystemWatched: " << filename
90                         << ". This should not happen.");
91         refresh();
92 }
93
94
95 FileMonitorGuard::~FileMonitorGuard()
96 {
97         qwatcher_->removePath(toqstr(filename_));
98 }
99
100
101 void FileMonitorGuard::refresh(bool new_file)
102 {
103         QString const qfilename = toqstr(filename_);
104         if(!qwatcher_->files().contains(qfilename)) {
105                 bool exists = QFile(qfilename).exists();
106                 if (!exists || !qwatcher_->addPath(qfilename)) {
107                         if (exists)
108                                 LYXERR(Debug::FILES,
109                                        "Could not add path to QFileSystemWatcher: "
110                                        << filename_);
111                         QTimer::singleShot(1000, this, [=](){
112                                         refresh(new_file || !exists);
113                                 });
114                 } else if (exists && new_file)
115                         Q_EMIT fileChanged();
116         }
117 }
118
119
120 void FileMonitorGuard::notifyChange(QString const & path)
121 {
122         if (path == toqstr(filename_)) {
123                 Q_EMIT fileChanged();
124                 // If the file has been modified by delete-move, we are notified of the
125                 // deletion but we no longer track the file. See
126                 // <https://bugreports.qt.io/browse/QTBUG-46483> (not a bug).
127                 refresh();
128         }
129 }
130
131
132 FileMonitor::FileMonitor(std::shared_ptr<FileMonitorGuard> monitor)
133         : monitor_(monitor)
134 {
135         connectToFileMonitorGuard();
136         refresh();
137 }
138
139
140 void FileMonitor::connectToFileMonitorGuard()
141 {
142         QObject::connect(monitor_.get(), SIGNAL(fileChanged()),
143                          this, SLOT(changed()));
144 }
145
146
147 boost::signals2::connection
148 FileMonitor::connect(sig::slot_type const & slot)
149 {
150         return fileChanged_.connect(slot);
151 }
152
153
154 void FileMonitor::disconnect()
155 {
156         fileChanged_.disconnect_all_slots();
157         QObject::disconnect(this, SIGNAL(fileChanged()));
158 }
159
160
161 void FileMonitor::changed()
162 {
163         // emit boost signal
164         fileChanged_();
165         Q_EMIT fileChanged();
166 }
167
168
169 FileMonitorBlocker FileMonitor::block(int delay)
170 {
171         FileMonitorBlocker blocker = blocker_.lock();
172         if (!blocker)
173                 blocker_ = blocker = make_shared<FileMonitorBlockerGuard>(this);
174         blocker->setDelay(delay);
175         return blocker;
176 }
177
178
179 FileMonitorBlockerGuard::FileMonitorBlockerGuard(FileMonitor * parent)
180         : QObject(parent), parent_(parent), delay_(0)
181 {
182         QObject::disconnect(parent_->monitor_.get(), SIGNAL(fileChanged()),
183                             parent_, SLOT(changed()));
184 }
185
186
187 void FileMonitorBlockerGuard::setDelay(int delay)
188 {
189         delay_ = max(delay_, delay);
190 }
191
192
193 FileMonitorBlockerGuard::~FileMonitorBlockerGuard()
194 {
195         // closures can only copy local copies
196         FileMonitor * parent = parent_;
197         // parent is also our QObject::parent() so we are deleted before parent.
198         // Even if delay_ is 0, the QTimer is necessary. Indeed, the notifications
199         // from QFileSystemWatcher that we meant to ignore are not going to be
200         // treated immediately, so we must yield to give us the opportunity to
201         // ignore them.
202         QTimer::singleShot(delay_, parent, [parent]() {
203                         parent->connectToFileMonitorGuard();
204                 });
205 }
206
207 } // namespace support
208 } // namespace lyx
209
210 #include "moc_FileMonitor.cpp"