]> git.lyx.org Git - lyx.git/blob - src/support/FileMonitor.h
fdf2bca69df1f938d0833c8e9b62ac8707356707
[lyx.git] / src / support / FileMonitor.h
1 // -*- C++ -*-
2 /**
3  * \file FileMonitor.h
4  * This file is part of LyX, the document processor.
5  * Licence details can be found in the file COPYING.
6  *
7  * \author Angus Leeming
8  * \author Guillaume Munch
9  *
10  * Full author contact details are available in file CREDITS.
11  *
12  * FileMonitor monitors a file and informs a listener when that file has
13  * changed.
14  */
15
16 #ifndef FILEMONITOR_H
17 #define FILEMONITOR_H
18
19 #include "support/FileName.h"
20 #include "support/signals.h"
21
22 #include <memory>
23
24 #include <QFileSystemWatcher>
25 #include <QObject>
26 #include <QPointer>
27
28
29 namespace lyx {
30 namespace support {
31
32 ///
33 ///  FileMonitor, a file monitor based on QFileSystemWatcher
34 ///
35
36 class FileMonitor;
37 class ActiveFileMonitor;
38 class FileMonitorGuard;
39 typedef std::unique_ptr<FileMonitor> FileMonitorPtr;
40 typedef std::unique_ptr<ActiveFileMonitor> ActiveFileMonitorPtr;
41
42 ///
43 /// Watch a file:
44 ///   FileMonitorPtr monitor = FileSystemWatcher::monitor(file_with_path);
45 ///   monitor.connect(...); //(using boost::signals2), or:
46 ///   connect(monitor, SIGNAL(fileChanged()),...); // (using Qt)
47 ///
48 /// Remember that a unique_ptr is automatically deleted at the end of a scope if
49 /// it has not been moved, or when assigned. When that happens, the signal
50 /// object is deleted and therefore all the connections are closed. The file
51 /// ceases being tracked when all the monitors for a file have been deleted.
52 ///
53 /// Stop watching:
54 ///   * as determined statically by the scope, or
55 ///   * dynamically, using:
56 ///       monitor = nullptr;
57 ///
58 /// Watch a different file:
59 ///   monitor = FileSystemWatcher::monitor(file_with_path2);
60 ///   monitor.connect(...);
61 /// (stops watching the first)
62 ///
63 /// Block notifications for the duration of a scope:
64 ///   {
65 ///       FileMonitorBlocker block = monitor.block();
66 ///       ...
67 ///   }
68 ///
69 /// Reset connections:
70 ///   monitor.disconnect();
71 ///   or the disconnect method of the connection object for the boost signal.
72 ///
73 class FileSystemWatcher
74 {
75 public:
76         /// as described above
77         static FileMonitorPtr monitor(FileName const & filename);
78         /// same but with an ActiveFileMonitor
79         static ActiveFileMonitorPtr activeMonitor(FileName const & filename,
80                                                   int interval = 10000);
81         /// Output whether the paths tracked by qwatcher_ and the active
82         /// FileMonitorGuards are in correspondence.
83         static void debug();
84 private:
85         FileSystemWatcher();
86         /// A global instance is created automatically on first call
87         static FileSystemWatcher & instance();
88         ///
89         std::shared_ptr<FileMonitorGuard> getGuard(FileName const & filename);
90         /// Caches the monitor guards but allow them to be destroyed
91         std::map<std::string, std::weak_ptr<FileMonitorGuard>> store_;
92         /// This class is a wrapper for QFileSystemWatcher
93         std::unique_ptr<QFileSystemWatcher> const qwatcher_;
94 };
95
96
97 /// Must be unique per path
98 /// Ends the watch when deleted
99 class FileMonitorGuard : public QObject
100 {
101         Q_OBJECT
102
103 public:
104         /// Start the watch
105         FileMonitorGuard(std::string const & filename,
106                          QFileSystemWatcher * qwatcher);
107         /// End the watch
108         ~FileMonitorGuard();
109         /// absolute path being tracked
110         std::string const & filename() { return filename_; }
111
112 public Q_SLOTS:
113         /// Make sure it is being monitored, after e.g. a deletion. See
114         /// <https://bugreports.qt.io/browse/QTBUG-46483>. This is called
115         /// automatically.
116         void refresh(bool emit = true);
117
118 Q_SIGNALS:
119         /// Connect to this to be notified when the file changes
120         void fileChanged(bool exists) const;
121
122 private Q_SLOTS:
123         /// Receive notifications from the QFileSystemWatcher
124         void notifyChange(QString const & path);
125
126 private:
127         std::string const filename_;
128         QFileSystemWatcher * qwatcher_;
129         /// for emitting fileChanged() when the file is created or deleted
130         bool exists_;
131 };
132
133
134 class FileMonitorBlockerGuard : public QObject
135 {
136         Q_OBJECT
137         QPointer<FileMonitor> monitor_;
138         int delay_;
139
140 public:
141         FileMonitorBlockerGuard(FileMonitor * monitor);
142         ~FileMonitorBlockerGuard();
143         void setDelay(int delay);
144 };
145
146
147 typedef std::shared_ptr<FileMonitorBlockerGuard> FileMonitorBlocker;
148
149
150 /// Main class
151 class FileMonitor : public QObject
152 {
153         Q_OBJECT
154         friend class FileMonitorBlockerGuard;
155
156 public:
157         FileMonitor(std::shared_ptr<FileMonitorGuard> monitor);
158
159         typedef signals2::signal<void(bool)> sig;
160         typedef sig::slot_type slot;
161         /// Connect and you'll be informed when the file has changed.
162         signals2::connection connect(slot const &);
163         /// disconnect all slots connected to the boost signal fileChanged_ or to
164         /// the qt signal fileChanged()
165         void disconnect();
166         /// absolute path being tracked
167         std::string const & filename() { return monitor_->filename(); }
168         /// Creates a guard that blocks notifications. Copyable. Notifications from
169         /// this monitor are blocked as long as there are copies of the
170         /// FileMonitorBlocker around.
171         /// \param delay is the amount waited in ms after expiration of the guard
172         /// before reconnecting. It can be used to slow down incoming events
173         /// accordingly. A value of 0 is still made asynchronous, because of the
174         /// fundamentally asynchronous nature of QFileSystemWatcher. To catch one's
175         /// own file operations, a value of 0 for delay is sufficient with the
176         /// inotify backend (e.g. Linux); for OSX (kqueue), a value of 100ms is
177         /// unsufficient and more tests need to be done in combination with
178         /// flushing/syncing to disk in order to understand how to catch one's own
179         /// operations reliably. No feedback about Windows. See
180         /// <https://www.mail-archive.com/lyx-devel@lists.lyx.org/msg200252.html>.
181         FileMonitorBlocker block(int delay = 0);
182         /// Make sure the good file is being monitored, after e.g. a move or a
183         /// deletion. See <https://bugreports.qt.io/browse/QTBUG-46483>. This is
184         /// called automatically.
185         void refresh() { monitor_->refresh(); }
186
187 Q_SIGNALS:
188         /// Connect to this to be notified when the file changes
189         void fileChanged(bool exists) const;
190
191 protected Q_SLOTS:
192         /// Receive notifications from the FileMonitorGuard
193         void changed(bool exists);
194         ///
195         void connectToFileMonitorGuard();
196
197 private:
198         /// boost signal
199         sig fileChanged_;
200         /// the unique watch for our file
201         std::shared_ptr<FileMonitorGuard> const monitor_;
202         ///
203         std::weak_ptr<FileMonitorBlockerGuard> blocker_;
204 };
205
206
207 /// When a more active monitoring style is needed.
208 /// For instance because QFileSystemWatcher does not work for remote file
209 /// systems.
210 class ActiveFileMonitor : public FileMonitor
211 {
212         Q_OBJECT
213 public:
214         ActiveFileMonitor(std::shared_ptr<FileMonitorGuard> monitor,
215                           FileName const & filename, int interval);
216         /// call checkModified asynchronously
217         void checkModifiedAsync();
218
219 public Q_SLOTS:
220         /// Check explicitly for a modification, but not more than once every
221         /// interval ms.
222         void checkModified();
223
224 private Q_SLOTS:
225         void setCooldown() { cooldown_ = true; }
226         void clearCooldown() { cooldown_ = false; }
227
228 private:
229         FileName const filename_;
230         ///
231         int const interval_;
232         ///
233         time_t timestamp_;
234         ///
235         unsigned long checksum_;
236         ///
237         bool cooldown_;
238 };
239
240
241 } // namespace support
242 } // namespace lyx
243
244 #endif // FILEMONITOR_H