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"