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
30namespace 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.
34class QFileSystemWatcherRelay : public QObject
35{
36 Q_OBJECT
37public:
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
56Q_SIGNALS:
57 void directoryChanged(const QString &path);
58 void fileChanged(const QString &path);
59};
60
61//----------------------------------------------------------------------------
62// DirWatch
63//----------------------------------------------------------------------------
64class DirWatch::Private : public QObject
65{
66 Q_OBJECT
67public:
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
81public Q_SLOTS:
82 void watcher_changed(const QString &path)
83 {
84 Q_UNUSED(path);
85 emit q->changed();
86 }
87};
88
89DirWatch::DirWatch(const QString &dir, QObject *parent)
90 : QObject(parent)
91{
92 d = new Private(this);
93 setDirName(dir);
94}
95
96DirWatch::~DirWatch()
97{
98 delete d;
99}
100
101QString DirWatch::dirName() const
102{
103 return d->dirName;
104}
105
106void 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
130class FileWatch::Private : public QObject
131{
132 Q_OBJECT
133public:
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
206private 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
242FileWatch::FileWatch(const QString &file, QObject *parent)
243 : QObject(parent)
244{
245 d = new Private(this);
246 d->start(fileName: file);
247}
248
249FileWatch::~FileWatch()
250{
251 delete d;
252}
253
254QString FileWatch::fileName() const
255{
256 return d->fileName;
257}
258
259void FileWatch::setFileName(const QString &file)
260{
261 d->stop();
262 d->start(fileName: file);
263}
264
265}
266
267#include "dirwatch.moc"
268

source code of qca/src/support/dirwatch.cpp