503 lines
17 KiB
C++
503 lines
17 KiB
C++
/****************************************************************************
|
|
**
|
|
** Copyright (C) 2016 The Qt Company Ltd.
|
|
** Contact: https://www.qt.io/licensing/
|
|
**
|
|
** This file is part of the QtCore module of the Qt Toolkit.
|
|
**
|
|
** $QT_BEGIN_LICENSE:LGPL$
|
|
** Commercial License Usage
|
|
** Licensees holding valid commercial Qt licenses may use this file in
|
|
** accordance with the commercial license agreement provided with the
|
|
** Software or, alternatively, in accordance with the terms contained in
|
|
** a written agreement between you and The Qt Company. For licensing terms
|
|
** and conditions see https://www.qt.io/terms-conditions. For further
|
|
** information use the contact form at https://www.qt.io/contact-us.
|
|
**
|
|
** GNU Lesser General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU Lesser
|
|
** General Public License version 3 as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.LGPL3 included in the
|
|
** packaging of this file. Please review the following information to
|
|
** ensure the GNU Lesser General Public License version 3 requirements
|
|
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
|
|
**
|
|
** GNU General Public License Usage
|
|
** Alternatively, this file may be used under the terms of the GNU
|
|
** General Public License version 2.0 or (at your option) the GNU General
|
|
** Public license version 3 or any later version approved by the KDE Free
|
|
** Qt Foundation. The licenses are as published by the Free Software
|
|
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
|
|
** included in the packaging of this file. Please review the following
|
|
** information to ensure the GNU General Public License requirements will
|
|
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
|
|
** https://www.gnu.org/licenses/gpl-3.0.html.
|
|
**
|
|
** $QT_END_LICENSE$
|
|
**
|
|
****************************************************************************/
|
|
|
|
#include "qfilesystemwatcher.h"
|
|
#include "qfilesystemwatcher_p.h"
|
|
|
|
#include <qdatetime.h>
|
|
#include <qdir.h>
|
|
#include <qfileinfo.h>
|
|
#include <qloggingcategory.h>
|
|
#include <qset.h>
|
|
#include <qtimer.h>
|
|
|
|
#if (defined(Q_OS_LINUX) || defined(Q_OS_QNX)) && QT_CONFIG(inotify)
|
|
#define USE_INOTIFY
|
|
#endif
|
|
|
|
#include "qfilesystemwatcher_polling_p.h"
|
|
#if defined(Q_OS_WIN)
|
|
# include "qfilesystemwatcher_win_p.h"
|
|
#elif defined(USE_INOTIFY)
|
|
# include "qfilesystemwatcher_inotify_p.h"
|
|
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) || defined(QT_PLATFORM_UIKIT)
|
|
# include "qfilesystemwatcher_kqueue_p.h"
|
|
#elif defined(Q_OS_OSX)
|
|
# include "qfilesystemwatcher_fsevents_p.h"
|
|
#endif
|
|
|
|
#include <algorithm>
|
|
#include <iterator>
|
|
|
|
QT_BEGIN_NAMESPACE
|
|
|
|
Q_LOGGING_CATEGORY(lcWatcher, "qt.core.filesystemwatcher")
|
|
|
|
QFileSystemWatcherEngine *QFileSystemWatcherPrivate::createNativeEngine(QObject *parent)
|
|
{
|
|
#if defined(Q_OS_WIN)
|
|
return new QWindowsFileSystemWatcherEngine(parent);
|
|
#elif defined(USE_INOTIFY)
|
|
// there is a chance that inotify may fail on Linux pre-2.6.13 (August
|
|
// 2005), so we can't just new inotify directly.
|
|
return QInotifyFileSystemWatcherEngine::create(parent);
|
|
#elif defined(Q_OS_FREEBSD) || defined(Q_OS_NETBSD) || defined(Q_OS_OPENBSD) || defined(QT_PLATFORM_UIKIT)
|
|
return QKqueueFileSystemWatcherEngine::create(parent);
|
|
#elif defined(Q_OS_OSX)
|
|
return QFseventsFileSystemWatcherEngine::create(parent);
|
|
#else
|
|
Q_UNUSED(parent);
|
|
return 0;
|
|
#endif
|
|
}
|
|
|
|
QFileSystemWatcherPrivate::QFileSystemWatcherPrivate()
|
|
: native(0), poller(0)
|
|
{
|
|
}
|
|
|
|
void QFileSystemWatcherPrivate::init()
|
|
{
|
|
Q_Q(QFileSystemWatcher);
|
|
native = createNativeEngine(q);
|
|
if (native) {
|
|
QObject::connect(native,
|
|
SIGNAL(fileChanged(QString,bool)),
|
|
q,
|
|
SLOT(_q_fileChanged(QString,bool)));
|
|
QObject::connect(native,
|
|
SIGNAL(directoryChanged(QString,bool)),
|
|
q,
|
|
SLOT(_q_directoryChanged(QString,bool)));
|
|
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
|
|
QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
|
|
&QWindowsFileSystemWatcherEngine::driveLockForRemoval,
|
|
q, [this] (const QString &p) { _q_winDriveLockForRemoval(p); });
|
|
QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
|
|
&QWindowsFileSystemWatcherEngine::driveLockForRemovalFailed,
|
|
q, [this] (const QString &p) { _q_winDriveLockForRemovalFailed(p); });
|
|
QObject::connect(static_cast<QWindowsFileSystemWatcherEngine *>(native),
|
|
&QWindowsFileSystemWatcherEngine::driveRemoved,
|
|
q, [this] (const QString &p) { _q_winDriveRemoved(p); });
|
|
#endif // !Q_OS_WINRT
|
|
}
|
|
}
|
|
|
|
void QFileSystemWatcherPrivate::initPollerEngine()
|
|
{
|
|
if(poller)
|
|
return;
|
|
|
|
Q_Q(QFileSystemWatcher);
|
|
poller = new QPollingFileSystemWatcherEngine(q); // that was a mouthful
|
|
QObject::connect(poller,
|
|
SIGNAL(fileChanged(QString,bool)),
|
|
q,
|
|
SLOT(_q_fileChanged(QString,bool)));
|
|
QObject::connect(poller,
|
|
SIGNAL(directoryChanged(QString,bool)),
|
|
q,
|
|
SLOT(_q_directoryChanged(QString,bool)));
|
|
}
|
|
|
|
void QFileSystemWatcherPrivate::_q_fileChanged(const QString &path, bool removed)
|
|
{
|
|
Q_Q(QFileSystemWatcher);
|
|
qCDebug(lcWatcher) << "file changed" << path << "removed?" << removed << "watching?" << files.contains(path);
|
|
if (!files.contains(path)) {
|
|
// the path was removed after a change was detected, but before we delivered the signal
|
|
return;
|
|
}
|
|
if (removed)
|
|
files.removeAll(path);
|
|
emit q->fileChanged(path, QFileSystemWatcher::QPrivateSignal());
|
|
}
|
|
|
|
void QFileSystemWatcherPrivate::_q_directoryChanged(const QString &path, bool removed)
|
|
{
|
|
Q_Q(QFileSystemWatcher);
|
|
qCDebug(lcWatcher) << "directory changed" << path << "removed?" << removed << "watching?" << directories.contains(path);
|
|
if (!directories.contains(path)) {
|
|
// perhaps the path was removed after a change was detected, but before we delivered the signal
|
|
return;
|
|
}
|
|
if (removed)
|
|
directories.removeAll(path);
|
|
emit q->directoryChanged(path, QFileSystemWatcher::QPrivateSignal());
|
|
}
|
|
|
|
#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
|
|
|
|
void QFileSystemWatcherPrivate::_q_winDriveLockForRemoval(const QString &path)
|
|
{
|
|
// Windows: Request to lock a (removable/USB) drive for removal, release
|
|
// its paths under watch, temporarily storing them should the lock fail.
|
|
Q_Q(QFileSystemWatcher);
|
|
QStringList pathsToBeRemoved;
|
|
auto pred = [&path] (const QString &f) { return !f.startsWith(path, Qt::CaseInsensitive); };
|
|
std::remove_copy_if(files.cbegin(), files.cend(),
|
|
std::back_inserter(pathsToBeRemoved), pred);
|
|
std::remove_copy_if(directories.cbegin(), directories.cend(),
|
|
std::back_inserter(pathsToBeRemoved), pred);
|
|
if (!pathsToBeRemoved.isEmpty()) {
|
|
q->removePaths(pathsToBeRemoved);
|
|
temporarilyRemovedPaths.insert(path.at(0), pathsToBeRemoved);
|
|
}
|
|
}
|
|
|
|
void QFileSystemWatcherPrivate::_q_winDriveLockForRemovalFailed(const QString &path)
|
|
{
|
|
// Windows: Request to lock a (removable/USB) drive failed (blocked by other
|
|
// application), restore the watched paths.
|
|
Q_Q(QFileSystemWatcher);
|
|
if (!path.isEmpty()) {
|
|
const auto it = temporarilyRemovedPaths.find(path.at(0));
|
|
if (it != temporarilyRemovedPaths.end()) {
|
|
q->addPaths(it.value());
|
|
temporarilyRemovedPaths.erase(it);
|
|
}
|
|
}
|
|
}
|
|
|
|
void QFileSystemWatcherPrivate::_q_winDriveRemoved(const QString &path)
|
|
{
|
|
// Windows: Drive finally removed, clear out paths stored in lock request.
|
|
if (!path.isEmpty())
|
|
temporarilyRemovedPaths.remove(path.at(0));
|
|
}
|
|
#endif // Q_OS_WIN && !Q_OS_WINRT
|
|
|
|
/*!
|
|
\class QFileSystemWatcher
|
|
\inmodule QtCore
|
|
\brief The QFileSystemWatcher class provides an interface for monitoring files and directories for modifications.
|
|
\ingroup io
|
|
\since 4.2
|
|
\reentrant
|
|
|
|
QFileSystemWatcher monitors the file system for changes to files
|
|
and directories by watching a list of specified paths.
|
|
|
|
Call addPath() to watch a particular file or directory. Multiple
|
|
paths can be added using the addPaths() function. Existing paths can
|
|
be removed by using the removePath() and removePaths() functions.
|
|
|
|
QFileSystemWatcher examines each path added to it. Files that have
|
|
been added to the QFileSystemWatcher can be accessed using the
|
|
files() function, and directories using the directories() function.
|
|
|
|
The fileChanged() signal is emitted when a file has been modified,
|
|
renamed or removed from disk. Similarly, the directoryChanged()
|
|
signal is emitted when a directory or its contents is modified or
|
|
removed. Note that QFileSystemWatcher stops monitoring files once
|
|
they have been renamed or removed from disk, and directories once
|
|
they have been removed from disk.
|
|
|
|
\list
|
|
\li \b Notes:
|
|
\list
|
|
\li On systems running a Linux kernel without inotify support,
|
|
file systems that contain watched paths cannot be unmounted.
|
|
|
|
\li The act of monitoring files and directories for
|
|
modifications consumes system resources. This implies there is a
|
|
limit to the number of files and directories your process can
|
|
monitor simultaneously. On all BSD variants, for
|
|
example, an open file descriptor is required for each monitored
|
|
file. Some system limits the number of open file descriptors to 256
|
|
by default. This means that addPath() and addPaths() will fail if
|
|
your process tries to add more than 256 files or directories to
|
|
the file system monitor. Also note that your process may have
|
|
other file descriptors open in addition to the ones for files
|
|
being monitored, and these other open descriptors also count in
|
|
the total. \macos uses a different backend and does not
|
|
suffer from this issue.
|
|
\endlist
|
|
\endlist
|
|
|
|
\sa QFile, QDir
|
|
*/
|
|
|
|
|
|
/*!
|
|
Constructs a new file system watcher object with the given \a parent.
|
|
*/
|
|
QFileSystemWatcher::QFileSystemWatcher(QObject *parent)
|
|
: QObject(*new QFileSystemWatcherPrivate, parent)
|
|
{
|
|
d_func()->init();
|
|
}
|
|
|
|
/*!
|
|
Constructs a new file system watcher object with the given \a parent
|
|
which monitors the specified \a paths list.
|
|
*/
|
|
QFileSystemWatcher::QFileSystemWatcher(const QStringList &paths, QObject *parent)
|
|
: QObject(*new QFileSystemWatcherPrivate, parent)
|
|
{
|
|
d_func()->init();
|
|
addPaths(paths);
|
|
}
|
|
|
|
/*!
|
|
Destroys the file system watcher.
|
|
*/
|
|
QFileSystemWatcher::~QFileSystemWatcher()
|
|
{ }
|
|
|
|
/*!
|
|
Adds \a path to the file system watcher if \a path exists. The
|
|
path is not added if it does not exist, or if it is already being
|
|
monitored by the file system watcher.
|
|
|
|
If \a path specifies a directory, the directoryChanged() signal
|
|
will be emitted when \a path is modified or removed from disk;
|
|
otherwise the fileChanged() signal is emitted when \a path is
|
|
modified, renamed or removed.
|
|
|
|
If the watch was successful, true is returned.
|
|
|
|
Reasons for a watch failure are generally system-dependent, but
|
|
may include the resource not existing, access failures, or the
|
|
total watch count limit, if the platform has one.
|
|
|
|
\note There may be a system dependent limit to the number of
|
|
files and directories that can be monitored simultaneously.
|
|
If this limit is been reached, \a path will not be monitored,
|
|
and false is returned.
|
|
|
|
\sa addPaths(), removePath()
|
|
*/
|
|
bool QFileSystemWatcher::addPath(const QString &path)
|
|
{
|
|
if (path.isEmpty()) {
|
|
qWarning("QFileSystemWatcher::addPath: path is empty");
|
|
return true;
|
|
}
|
|
|
|
QStringList paths = addPaths(QStringList(path));
|
|
return paths.isEmpty();
|
|
}
|
|
|
|
static QStringList empty_paths_pruned(const QStringList &paths)
|
|
{
|
|
QStringList p;
|
|
p.reserve(paths.size());
|
|
const auto isEmpty = [](const QString &s) { return s.isEmpty(); };
|
|
std::remove_copy_if(paths.begin(), paths.end(),
|
|
std::back_inserter(p),
|
|
isEmpty);
|
|
return p;
|
|
}
|
|
|
|
/*!
|
|
Adds each path in \a paths to the file system watcher. Paths are
|
|
not added if they not exist, or if they are already being
|
|
monitored by the file system watcher.
|
|
|
|
If a path specifies a directory, the directoryChanged() signal
|
|
will be emitted when the path is modified or removed from disk;
|
|
otherwise the fileChanged() signal is emitted when the path is
|
|
modified, renamed, or removed.
|
|
|
|
The return value is a list of paths that could not be watched.
|
|
|
|
Reasons for a watch failure are generally system-dependent, but
|
|
may include the resource not existing, access failures, or the
|
|
total watch count limit, if the platform has one.
|
|
|
|
\note There may be a system dependent limit to the number of
|
|
files and directories that can be monitored simultaneously.
|
|
If this limit has been reached, the excess \a paths will not
|
|
be monitored, and they will be added to the returned QStringList.
|
|
|
|
\sa addPath(), removePaths()
|
|
*/
|
|
QStringList QFileSystemWatcher::addPaths(const QStringList &paths)
|
|
{
|
|
Q_D(QFileSystemWatcher);
|
|
|
|
QStringList p = empty_paths_pruned(paths);
|
|
|
|
if (p.isEmpty()) {
|
|
qWarning("QFileSystemWatcher::addPaths: list is empty");
|
|
return p;
|
|
}
|
|
qCDebug(lcWatcher) << "adding" << paths;
|
|
const auto selectEngine = [this, d]() -> QFileSystemWatcherEngine* {
|
|
#ifdef QT_BUILD_INTERNAL
|
|
const QString on = objectName();
|
|
|
|
if (Q_UNLIKELY(on.startsWith(QLatin1String("_qt_autotest_force_engine_")))) {
|
|
// Autotest override case - use the explicitly selected engine only
|
|
const QStringRef forceName = on.midRef(26);
|
|
if (forceName == QLatin1String("poller")) {
|
|
qCDebug(lcWatcher, "QFileSystemWatcher: skipping native engine, using only polling engine");
|
|
d_func()->initPollerEngine();
|
|
return d->poller;
|
|
} else if (forceName == QLatin1String("native")) {
|
|
qCDebug(lcWatcher, "QFileSystemWatcher: skipping polling engine, using only native engine");
|
|
return d->native;
|
|
}
|
|
return nullptr;
|
|
}
|
|
#endif
|
|
// Normal runtime case - search intelligently for best engine
|
|
if(d->native) {
|
|
return d->native;
|
|
} else {
|
|
d_func()->initPollerEngine();
|
|
return d->poller;
|
|
}
|
|
};
|
|
|
|
if (auto engine = selectEngine())
|
|
p = engine->addPaths(p, &d->files, &d->directories);
|
|
|
|
return p;
|
|
}
|
|
|
|
/*!
|
|
Removes the specified \a path from the file system watcher.
|
|
|
|
If the watch is successfully removed, true is returned.
|
|
|
|
Reasons for watch removal failing are generally system-dependent,
|
|
but may be due to the path having already been deleted, for example.
|
|
|
|
\sa removePaths(), addPath()
|
|
*/
|
|
bool QFileSystemWatcher::removePath(const QString &path)
|
|
{
|
|
if (path.isEmpty()) {
|
|
qWarning("QFileSystemWatcher::removePath: path is empty");
|
|
return true;
|
|
}
|
|
|
|
QStringList paths = removePaths(QStringList(path));
|
|
return paths.isEmpty();
|
|
}
|
|
|
|
/*!
|
|
Removes the specified \a paths from the file system watcher.
|
|
|
|
The return value is a list of paths which were not able to be
|
|
unwatched successfully.
|
|
|
|
Reasons for watch removal failing are generally system-dependent,
|
|
but may be due to the path having already been deleted, for example.
|
|
|
|
\sa removePath(), addPaths()
|
|
*/
|
|
QStringList QFileSystemWatcher::removePaths(const QStringList &paths)
|
|
{
|
|
Q_D(QFileSystemWatcher);
|
|
|
|
QStringList p = empty_paths_pruned(paths);
|
|
|
|
if (p.isEmpty()) {
|
|
qWarning("QFileSystemWatcher::removePaths: list is empty");
|
|
return p;
|
|
}
|
|
qCDebug(lcWatcher) << "removing" << paths;
|
|
|
|
if (d->native)
|
|
p = d->native->removePaths(p, &d->files, &d->directories);
|
|
if (d->poller)
|
|
p = d->poller->removePaths(p, &d->files, &d->directories);
|
|
|
|
return p;
|
|
}
|
|
|
|
/*!
|
|
\fn void QFileSystemWatcher::fileChanged(const QString &path)
|
|
|
|
This signal is emitted when the file at the specified \a path is
|
|
modified, renamed or removed from disk.
|
|
|
|
\sa directoryChanged()
|
|
*/
|
|
|
|
/*!
|
|
\fn void QFileSystemWatcher::directoryChanged(const QString &path)
|
|
|
|
This signal is emitted when the directory at a specified \a path
|
|
is modified (e.g., when a file is added or deleted) or removed
|
|
from disk. Note that if there are several changes during a short
|
|
period of time, some of the changes might not emit this signal.
|
|
However, the last change in the sequence of changes will always
|
|
generate this signal.
|
|
|
|
\sa fileChanged()
|
|
*/
|
|
|
|
/*!
|
|
\fn QStringList QFileSystemWatcher::directories() const
|
|
|
|
Returns a list of paths to directories that are being watched.
|
|
|
|
\sa files()
|
|
*/
|
|
|
|
/*!
|
|
\fn QStringList QFileSystemWatcher::files() const
|
|
|
|
Returns a list of paths to files that are being watched.
|
|
|
|
\sa directories()
|
|
*/
|
|
|
|
QStringList QFileSystemWatcher::directories() const
|
|
{
|
|
Q_D(const QFileSystemWatcher);
|
|
return d->directories;
|
|
}
|
|
|
|
QStringList QFileSystemWatcher::files() const
|
|
{
|
|
Q_D(const QFileSystemWatcher);
|
|
return d->files;
|
|
}
|
|
|
|
QT_END_NAMESPACE
|
|
|
|
#include "moc_qfilesystemwatcher.cpp"
|
|
#include "moc_qfilesystemwatcher_p.cpp"
|
|
|