1 | /* |
2 | * Copyright (C) 2003-2008 Justin Karneges <justin@affinix.com> |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2.1 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library; if not, write to the Free Software |
16 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
17 | * 02110-1301 USA |
18 | * |
19 | */ |
20 | |
21 | #include "qca_support.h" |
22 | |
23 | #include "qca_safeobj.h" |
24 | #include <QDateTime> |
25 | #include <QDir> |
26 | #include <QFileInfo> |
27 | #include <QFileSystemWatcher> |
28 | #include <QList> |
29 | |
30 | namespace QCA { |
31 | |
32 | // this gets us DOR-SS and SR, provided we delete the object between uses. |
33 | // we assume QFileSystemWatcher complies to DS,NE. |
34 | class QFileSystemWatcherRelay : public QObject |
35 | { |
36 | Q_OBJECT |
37 | public: |
38 | QFileSystemWatcher *watcher; |
39 | |
40 | QFileSystemWatcherRelay(QFileSystemWatcher *_watcher, QObject *parent = nullptr) |
41 | : QObject(parent) |
42 | , watcher(_watcher) |
43 | { |
44 | connect(sender: watcher, |
45 | signal: &QFileSystemWatcher::directoryChanged, |
46 | context: this, |
47 | slot: &QFileSystemWatcherRelay::directoryChanged, |
48 | type: Qt::QueuedConnection); |
49 | connect(sender: watcher, |
50 | signal: &QFileSystemWatcher::fileChanged, |
51 | context: this, |
52 | slot: &QFileSystemWatcherRelay::fileChanged, |
53 | type: Qt::QueuedConnection); |
54 | } |
55 | |
56 | Q_SIGNALS: |
57 | void directoryChanged(const QString &path); |
58 | void fileChanged(const QString &path); |
59 | }; |
60 | |
61 | //---------------------------------------------------------------------------- |
62 | // DirWatch |
63 | //---------------------------------------------------------------------------- |
64 | class DirWatch::Private : public QObject |
65 | { |
66 | Q_OBJECT |
67 | public: |
68 | DirWatch *q; |
69 | QFileSystemWatcher *watcher; |
70 | QFileSystemWatcherRelay *watcher_relay; |
71 | QString dirName; |
72 | |
73 | Private(DirWatch *_q) |
74 | : QObject(_q) |
75 | , q(_q) |
76 | , watcher(nullptr) |
77 | , watcher_relay(nullptr) |
78 | { |
79 | } |
80 | |
81 | public Q_SLOTS: |
82 | void watcher_changed(const QString &path) |
83 | { |
84 | Q_UNUSED(path); |
85 | emit q->changed(); |
86 | } |
87 | }; |
88 | |
89 | DirWatch::DirWatch(const QString &dir, QObject *parent) |
90 | : QObject(parent) |
91 | { |
92 | d = new Private(this); |
93 | setDirName(dir); |
94 | } |
95 | |
96 | DirWatch::~DirWatch() |
97 | { |
98 | delete d; |
99 | } |
100 | |
101 | QString DirWatch::dirName() const |
102 | { |
103 | return d->dirName; |
104 | } |
105 | |
106 | void DirWatch::setDirName(const QString &dir) |
107 | { |
108 | if (d->watcher) { |
109 | delete d->watcher; |
110 | delete d->watcher_relay; |
111 | d->watcher = nullptr; |
112 | d->watcher_relay = nullptr; |
113 | } |
114 | |
115 | d->dirName = dir; |
116 | |
117 | if (!d->dirName.isEmpty() && QFileInfo(d->dirName).isDir()) { |
118 | d->watcher = new QFileSystemWatcher(this); |
119 | d->watcher_relay = new QFileSystemWatcherRelay(d->watcher, this); |
120 | connect(sender: d->watcher_relay, signal: &QFileSystemWatcherRelay::directoryChanged, context: d, slot: &Private::watcher_changed); |
121 | |
122 | d->watcher->addPath(file: d->dirName); |
123 | } |
124 | } |
125 | |
126 | //---------------------------------------------------------------------------- |
127 | // FileWatch |
128 | //---------------------------------------------------------------------------- |
129 | |
130 | class FileWatch::Private : public QObject |
131 | { |
132 | Q_OBJECT |
133 | public: |
134 | FileWatch *q; |
135 | QFileSystemWatcher *watcher; |
136 | QFileSystemWatcherRelay *watcher_relay; |
137 | QString fileName; // file (optionally w/ path) as provided by user |
138 | QString filePath; // absolute path of file, calculated by us |
139 | bool fileExisted; |
140 | |
141 | Private(FileWatch *_q) |
142 | : QObject(_q) |
143 | , q(_q) |
144 | , watcher(nullptr) |
145 | , watcher_relay(nullptr) |
146 | { |
147 | } |
148 | |
149 | void start(const QString &_fileName) |
150 | { |
151 | fileName = _fileName; |
152 | |
153 | watcher = new QFileSystemWatcher(this); |
154 | watcher_relay = new QFileSystemWatcherRelay(watcher, this); |
155 | connect(sender: watcher_relay, signal: &QFileSystemWatcherRelay::directoryChanged, context: this, slot: &Private::dir_changed); |
156 | connect(sender: watcher_relay, signal: &QFileSystemWatcherRelay::fileChanged, context: this, slot: &Private::file_changed); |
157 | |
158 | QFileInfo fi(fileName); |
159 | fi.makeAbsolute(); |
160 | filePath = fi.filePath(); |
161 | const QDir dir = fi.dir(); |
162 | |
163 | // we watch both the directory and the file itself. the |
164 | // reason we watch the directory is so we can detect when |
165 | // the file is deleted/created |
166 | |
167 | // we don't bother checking for dir existence before adding, |
168 | // since there isn't an atomic way to do both at once. if |
169 | // it turns out that the dir doesn't exist, then the |
170 | // monitoring will just silently not work at all. |
171 | |
172 | watcher->addPath(file: dir.path()); |
173 | |
174 | // can't watch for non-existent directory |
175 | if (!watcher->directories().contains(str: dir.path())) { |
176 | stop(); |
177 | return; |
178 | } |
179 | |
180 | // save whether or not the file exists |
181 | fileExisted = fi.exists(); |
182 | |
183 | // add only if file existent |
184 | // if no it will be added on directoryChanged signal |
185 | if (fileExisted) |
186 | watcher->addPath(file: filePath); |
187 | |
188 | // TODO: address race conditions and think about error |
189 | // reporting instead of silently failing. probably this |
190 | // will require a Qt API update. |
191 | } |
192 | |
193 | void stop() |
194 | { |
195 | if (watcher) { |
196 | delete watcher; |
197 | delete watcher_relay; |
198 | watcher = nullptr; |
199 | watcher_relay = nullptr; |
200 | } |
201 | |
202 | fileName.clear(); |
203 | filePath.clear(); |
204 | } |
205 | |
206 | private Q_SLOTS: |
207 | void dir_changed(const QString &path) |
208 | { |
209 | Q_UNUSED(path); |
210 | QFileInfo fi(filePath); |
211 | const bool exists = fi.exists(); |
212 | if (exists && !fileExisted) { |
213 | // this means the file was created. put a |
214 | // watch on it. |
215 | fileExisted = true; |
216 | watcher->addPath(file: filePath); |
217 | emit q->changed(); |
218 | } |
219 | } |
220 | |
221 | void file_changed(const QString &path) |
222 | { |
223 | Q_UNUSED(path); |
224 | QFileInfo fi(filePath); |
225 | if (!fi.exists() && !fileExisted) { |
226 | // Got a file changed signal on a file that does not exist |
227 | // and is not actively watched. This happens when we |
228 | // previously watched a file but it was deleted and after |
229 | // the original deletion changed-signal we get another one |
230 | // (for example because of bad signal timing). In this scenario |
231 | // we must ignore the change as the change, whatever it may |
232 | // have been, is of no interest to us because we don't watch |
233 | // the file and furthermore the file does not even exist. |
234 | return; |
235 | } else if (!fi.exists()) { |
236 | fileExisted = false; |
237 | }; |
238 | emit q->changed(); |
239 | } |
240 | }; |
241 | |
242 | FileWatch::FileWatch(const QString &file, QObject *parent) |
243 | : QObject(parent) |
244 | { |
245 | d = new Private(this); |
246 | d->start(fileName: file); |
247 | } |
248 | |
249 | FileWatch::~FileWatch() |
250 | { |
251 | delete d; |
252 | } |
253 | |
254 | QString FileWatch::fileName() const |
255 | { |
256 | return d->fileName; |
257 | } |
258 | |
259 | void FileWatch::setFileName(const QString &file) |
260 | { |
261 | d->stop(); |
262 | d->start(fileName: file); |
263 | } |
264 | |
265 | } |
266 | |
267 | #include "dirwatch.moc" |
268 | |