]> git.lyx.org Git - lyx.git/blob - src/support/FileMonitor.h
768e61f8733f91254895efca25e6259ab8818de2
[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 Guillaume Munch
8  *
9  * Full author contact details are available in file CREDITS.
10  *
11  * FileMonitor monitors a file and informs a listener when that file has
12  * changed.
13  */
14
15 #ifndef FILEMONITOR_H
16 #define FILEMONITOR_H
17
18 #include <memory>
19
20 #include <QFileSystemWatcher>
21 #include <QObject>
22 #include <QPointer>
23
24 #include <boost/signals2.hpp>
25
26
27 namespace lyx {
28 namespace support {
29
30 class FileName;
31
32 ///
33 ///  FileMonitor, a file monitor based on QFileSystemWatcher
34 ///
35
36 class FileMonitor;
37 class FileMonitorGuard;
38 typedef std::unique_ptr<FileMonitor> FileMonitorPtr;
39
40 ///
41 /// Watch a file:
42 ///   FileMonitorPtr monitor = FileSystemWatcher::monitor(file_with_path);
43 ///   monitor.connect(...); //(using boost::signals2), or:
44 ///   connect(monitor, SIGNAL(fileChanged()),...); // (using Qt)
45 ///
46 /// Remember that a unique_ptr is automatically deleted at the end of a scope if
47 /// it has not been moved, or when assigned. When that happens, the signal
48 /// object is deleted and therefore all the connections are closed. The file
49 /// ceases being tracked when all the monitors for a file have been deleted.
50 ///
51 /// Stop watching:
52 ///   * as determined statically by the scope, or
53 ///   * dynamically, using:
54 ///       monitor = nullptr;
55 ///
56 /// Watch a different file:
57 ///   monitor = FileSystemWatcher::monitor(file_with_path2);
58 ///   monitor.connect(...);
59 /// (stops watching the first)
60 ///
61 /// Block notifications for the duration of a scope:
62 ///   {
63 ///       FileMonitorBlocker block = monitor.block();
64 ///       ...
65 ///   }
66 ///
67 /// Reset connections:
68 ///   monitor.disconnect();
69 ///   or the disconnect method of the connection object for the boost signal.
70 ///
71 class FileSystemWatcher
72 {
73 public:
74         // as described above
75         static FileMonitorPtr monitor(FileName const & file_with_path);
76         // Output whether the paths tracked by qwatcher_ and the active
77         // FileMonitorGuards are in correspondence.
78         static void debug();
79 private:
80         FileSystemWatcher();
81         // A global instance is created automatically on first call to monitor
82         static FileSystemWatcher & instance();
83         // Caches the monitor guards but allow them to be destroyed
84         std::map<std::string, std::weak_ptr<FileMonitorGuard>> store_;
85         // This class is a wrapper for QFileSystemWatcher
86         std::unique_ptr<QFileSystemWatcher> const qwatcher_;
87 };
88
89
90 // Must be unique per path
91 // Ends the watch when deleted
92 class FileMonitorGuard : public QObject
93 {
94         Q_OBJECT
95
96 public:
97         /// Start the watch
98         FileMonitorGuard(std::string const & filename,
99                          QFileSystemWatcher * qwatcher);
100         /// End the watch
101         ~FileMonitorGuard();
102         /// absolute path being tracked
103         std::string const & filename() { return filename_; }
104         /// Make sure it is being monitored, after e.g. a deletion. See
105         /// <https://bugreports.qt.io/browse/QTBUG-46483>. This is called
106         /// automatically.
107         /// \param new_file  If true, emit fileChanged if the file exists and was
108         /// successfully added.
109         void refresh(bool new_file = false);
110
111 Q_SIGNALS:
112         /// Connect to this to be notified when the file changes
113         void fileChanged() const;
114
115 private Q_SLOTS:
116         /// Receive notifications from the QFileSystemWatcher
117         void notifyChange(QString const & path);
118
119         /// nonsense introduced for when QT_VERSION < 0x050000, cannot be placed
120         /// between #ifdef
121         void refreshTrue() { refresh(true); }
122         /// nonsense introduced for when QT_VERSION < 0x050000, cannot be placed
123         /// between #ifdef
124         void refreshFalse() { refresh(false); }
125
126
127 private:
128         std::string const filename_;
129         QFileSystemWatcher * qwatcher_;
130 };
131
132
133 class FileMonitorBlockerGuard : public QObject
134 {
135         Q_OBJECT
136         QPointer<FileMonitor> monitor_;
137         int delay_;
138
139 public:
140         FileMonitorBlockerGuard(FileMonitor * monitor);
141         ~FileMonitorBlockerGuard();
142         void setDelay(int delay);
143 };
144
145
146 typedef std::shared_ptr<FileMonitorBlockerGuard> FileMonitorBlocker;
147
148
149 /// Main class
150 class FileMonitor : public QObject
151 {
152         Q_OBJECT
153         friend class FileMonitorBlockerGuard;
154
155 public:
156         FileMonitor(std::shared_ptr<FileMonitorGuard> monitor);
157
158         typedef boost::signals2::signal<void()> sig;
159         /// Connect and you'll be informed when the file has changed.
160         boost::signals2::connection connect(sig::slot_type const &);
161         /// disconnect all slots connected to the boost signal fileChanged_ or to
162         /// the qt signal fileChanged()
163         void disconnect();
164         /// absolute path being tracked
165         std::string const & filename() { return monitor_->filename(); }
166         /// Creates a guard that blocks notifications. Copyable. Notifications from
167         /// this monitor are blocked as long as there are copies around.
168         /// \param delay is the amount waited in ms after expiration of the guard
169         /// before reconnecting. This delay thing is to deal with asynchronous
170         /// notifications in a not so elegant fashion. But it can also be used to
171         /// slow down incoming events.
172         FileMonitorBlocker block(int delay = 0);
173         /// Make sure the good file is being monitored, after e.g. a move or a
174         /// deletion. See <https://bugreports.qt.io/browse/QTBUG-46483>. This is
175         /// called automatically.
176         void refresh() { return monitor_->refresh(); }
177
178 Q_SIGNALS:
179         /// Connect to this to be notified when the file changes
180         void fileChanged() const;
181
182 private Q_SLOTS:
183         /// Receive notifications from the FileMonitorGuard
184         void changed();
185         ///
186         void connectToFileMonitorGuard();
187
188 private:
189         // boost signal
190         sig fileChanged_;
191         // the unique watch for our file
192         std::shared_ptr<FileMonitorGuard> const monitor_;
193         //
194         std::weak_ptr<FileMonitorBlockerGuard> blocker_;
195 };
196
197
198
199 } // namespace support
200 } // namespace lyx
201
202 #endif // FILEMONITOR_H