File indexing completed on 2024-12-01 07:27:41
0001 /* 0002 * Copyright (C) 2003-2008 Justin Karneges <justin@affinix.com> 0003 * 0004 * This library is free software; you can redistribute it and/or 0005 * modify it under the terms of the GNU Lesser General Public 0006 * License as published by the Free Software Foundation; either 0007 * version 2.1 of the License, or (at your option) any later version. 0008 * 0009 * This library is distributed in the hope that it will be useful, 0010 * but WITHOUT ANY WARRANTY; without even the implied warranty of 0011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 0012 * Lesser General Public License for more details. 0013 * 0014 * You should have received a copy of the GNU Lesser General Public 0015 * License along with this library; if not, write to the Free Software 0016 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 0017 * 02110-1301 USA 0018 * 0019 */ 0020 0021 #include "qca_support.h" 0022 0023 #include "qca_safeobj.h" 0024 #include <QDateTime> 0025 #include <QDir> 0026 #include <QFileInfo> 0027 #include <QFileSystemWatcher> 0028 #include <QList> 0029 0030 namespace QCA { 0031 0032 // this gets us DOR-SS and SR, provided we delete the object between uses. 0033 // we assume QFileSystemWatcher complies to DS,NE. 0034 class QFileSystemWatcherRelay : public QObject 0035 { 0036 Q_OBJECT 0037 public: 0038 QFileSystemWatcher *watcher; 0039 0040 QFileSystemWatcherRelay(QFileSystemWatcher *_watcher, QObject *parent = nullptr) 0041 : QObject(parent) 0042 , watcher(_watcher) 0043 { 0044 connect(watcher, 0045 &QFileSystemWatcher::directoryChanged, 0046 this, 0047 &QFileSystemWatcherRelay::directoryChanged, 0048 Qt::QueuedConnection); 0049 connect(watcher, 0050 &QFileSystemWatcher::fileChanged, 0051 this, 0052 &QFileSystemWatcherRelay::fileChanged, 0053 Qt::QueuedConnection); 0054 } 0055 0056 Q_SIGNALS: 0057 void directoryChanged(const QString &path); 0058 void fileChanged(const QString &path); 0059 }; 0060 0061 //---------------------------------------------------------------------------- 0062 // DirWatch 0063 //---------------------------------------------------------------------------- 0064 class DirWatch::Private : public QObject 0065 { 0066 Q_OBJECT 0067 public: 0068 DirWatch *q; 0069 QFileSystemWatcher *watcher; 0070 QFileSystemWatcherRelay *watcher_relay; 0071 QString dirName; 0072 0073 Private(DirWatch *_q) 0074 : QObject(_q) 0075 , q(_q) 0076 , watcher(nullptr) 0077 , watcher_relay(nullptr) 0078 { 0079 } 0080 0081 public Q_SLOTS: 0082 void watcher_changed(const QString &path) 0083 { 0084 Q_UNUSED(path); 0085 emit q->changed(); 0086 } 0087 }; 0088 0089 DirWatch::DirWatch(const QString &dir, QObject *parent) 0090 : QObject(parent) 0091 { 0092 d = new Private(this); 0093 setDirName(dir); 0094 } 0095 0096 DirWatch::~DirWatch() 0097 { 0098 delete d; 0099 } 0100 0101 QString DirWatch::dirName() const 0102 { 0103 return d->dirName; 0104 } 0105 0106 void DirWatch::setDirName(const QString &dir) 0107 { 0108 if (d->watcher) { 0109 delete d->watcher; 0110 delete d->watcher_relay; 0111 d->watcher = nullptr; 0112 d->watcher_relay = nullptr; 0113 } 0114 0115 d->dirName = dir; 0116 0117 if (!d->dirName.isEmpty() && QFileInfo(d->dirName).isDir()) { 0118 d->watcher = new QFileSystemWatcher(this); 0119 d->watcher_relay = new QFileSystemWatcherRelay(d->watcher, this); 0120 connect(d->watcher_relay, &QFileSystemWatcherRelay::directoryChanged, d, &Private::watcher_changed); 0121 0122 d->watcher->addPath(d->dirName); 0123 } 0124 } 0125 0126 //---------------------------------------------------------------------------- 0127 // FileWatch 0128 //---------------------------------------------------------------------------- 0129 0130 class FileWatch::Private : public QObject 0131 { 0132 Q_OBJECT 0133 public: 0134 FileWatch *q; 0135 QFileSystemWatcher *watcher; 0136 QFileSystemWatcherRelay *watcher_relay; 0137 QString fileName; // file (optionally w/ path) as provided by user 0138 QString filePath; // absolute path of file, calculated by us 0139 bool fileExisted; 0140 0141 Private(FileWatch *_q) 0142 : QObject(_q) 0143 , q(_q) 0144 , watcher(nullptr) 0145 , watcher_relay(nullptr) 0146 { 0147 } 0148 0149 void start(const QString &_fileName) 0150 { 0151 fileName = _fileName; 0152 0153 watcher = new QFileSystemWatcher(this); 0154 watcher_relay = new QFileSystemWatcherRelay(watcher, this); 0155 connect(watcher_relay, &QFileSystemWatcherRelay::directoryChanged, this, &Private::dir_changed); 0156 connect(watcher_relay, &QFileSystemWatcherRelay::fileChanged, this, &Private::file_changed); 0157 0158 QFileInfo fi(fileName); 0159 fi.makeAbsolute(); 0160 filePath = fi.filePath(); 0161 const QDir dir = fi.dir(); 0162 0163 // we watch both the directory and the file itself. the 0164 // reason we watch the directory is so we can detect when 0165 // the file is deleted/created 0166 0167 // we don't bother checking for dir existence before adding, 0168 // since there isn't an atomic way to do both at once. if 0169 // it turns out that the dir doesn't exist, then the 0170 // monitoring will just silently not work at all. 0171 0172 watcher->addPath(dir.path()); 0173 0174 // can't watch for non-existent directory 0175 if (!watcher->directories().contains(dir.path())) { 0176 stop(); 0177 return; 0178 } 0179 0180 // save whether or not the file exists 0181 fileExisted = fi.exists(); 0182 0183 // add only if file existent 0184 // if no it will be added on directoryChanged signal 0185 if (fileExisted) 0186 watcher->addPath(filePath); 0187 0188 // TODO: address race conditions and think about error 0189 // reporting instead of silently failing. probably this 0190 // will require a Qt API update. 0191 } 0192 0193 void stop() 0194 { 0195 if (watcher) { 0196 delete watcher; 0197 delete watcher_relay; 0198 watcher = nullptr; 0199 watcher_relay = nullptr; 0200 } 0201 0202 fileName.clear(); 0203 filePath.clear(); 0204 } 0205 0206 private Q_SLOTS: 0207 void dir_changed(const QString &path) 0208 { 0209 Q_UNUSED(path); 0210 QFileInfo fi(filePath); 0211 const bool exists = fi.exists(); 0212 if (exists && !fileExisted) { 0213 // this means the file was created. put a 0214 // watch on it. 0215 fileExisted = true; 0216 watcher->addPath(filePath); 0217 emit q->changed(); 0218 } 0219 } 0220 0221 void file_changed(const QString &path) 0222 { 0223 Q_UNUSED(path); 0224 QFileInfo fi(filePath); 0225 if (!fi.exists() && !fileExisted) { 0226 // Got a file changed signal on a file that does not exist 0227 // and is not actively watched. This happens when we 0228 // previously watched a file but it was deleted and after 0229 // the original deletion changed-signal we get another one 0230 // (for example because of bad signal timing). In this scenario 0231 // we must ignore the change as the change, whatever it may 0232 // have been, is of no interest to us because we don't watch 0233 // the file and furthermore the file does not even exist. 0234 return; 0235 } else if (!fi.exists()) { 0236 fileExisted = false; 0237 }; 0238 emit q->changed(); 0239 } 0240 }; 0241 0242 FileWatch::FileWatch(const QString &file, QObject *parent) 0243 : QObject(parent) 0244 { 0245 d = new Private(this); 0246 d->start(file); 0247 } 0248 0249 FileWatch::~FileWatch() 0250 { 0251 delete d; 0252 } 0253 0254 QString FileWatch::fileName() const 0255 { 0256 return d->fileName; 0257 } 0258 0259 void FileWatch::setFileName(const QString &file) 0260 { 0261 d->stop(); 0262 d->start(file); 0263 } 0264 0265 } 0266 0267 #include "dirwatch.moc"