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