QFileSystemWatcher/Windows: Recreate handle for files

QWindowsFileSystemWatcherEngine uses one change notification
per directory to watch directories or files within that
directory. Adding files and their directories in a sequence
caused the value in QWindowsFileSystemWatcherEngineThread::HandleForDirHash
to be overwritten.
Relax the check for the flags (watcher attributes) to use >= and recreate
the change notification of a directory should its flags be insufficient.
This triggers when a file is added after its directory since files
require more attributes.

Task-number: QTBUG-61792
Change-Id: I371a72f1934fa82c53aaf84beb907825031f1c81
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
Friedemann Kleint 2017-07-06 12:38:31 +02:00
parent 7beb2bc02b
commit e1d7f7dfbc
2 changed files with 42 additions and 9 deletions

View File

@ -71,6 +71,19 @@ QT_BEGIN_NAMESPACE
# define DEBUG if (false) qDebug
#endif
static Qt::HANDLE createChangeNotification(const QString &path, uint flags)
{
// Volume and folder paths need a trailing slash for proper notification
// (e.g. "c:" -> "c:/").
QString nativePath = QDir::toNativeSeparators(path);
if ((flags & FILE_NOTIFY_CHANGE_ATTRIBUTES) == 0 && !nativePath.endsWith(QLatin1Char('\\')))
nativePath.append(QLatin1Char('\\'));
const HANDLE result = FindFirstChangeNotification(reinterpret_cast<const wchar_t *>(nativePath.utf16()),
FALSE, flags);
DEBUG() << __FUNCTION__ << nativePath << hex <<showbase << flags << "returns" << result;
return result;
}
#ifndef Q_OS_WINRT
///////////
// QWindowsRemovableDriveListener
@ -404,8 +417,29 @@ QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths,
thread = *jt;
QMutexLocker locker(&(thread->mutex));
handle = thread->handleForDir.value(QFileSystemWatcherPathKey(absolutePath));
if (handle.handle != INVALID_HANDLE_VALUE && handle.flags == flags) {
const auto hit = thread->handleForDir.find(QFileSystemWatcherPathKey(absolutePath));
if (hit != thread->handleForDir.end() && hit.value().flags < flags) {
// Requesting to add a file whose directory has been added previously.
// Recreate the notification handle to add the missing notification attributes
// for files (FILE_NOTIFY_CHANGE_ATTRIBUTES...)
DEBUG() << "recreating" << absolutePath << hex << showbase << hit.value().flags
<< "->" << flags;
const Qt::HANDLE fileHandle = createChangeNotification(absolutePath, flags);
if (fileHandle != INVALID_HANDLE_VALUE) {
const int index = thread->handles.indexOf(hit.value().handle);
const auto pit = thread->pathInfoForHandle.find(hit.value().handle);
Q_ASSERT(index != -1);
Q_ASSERT(pit != thread->pathInfoForHandle.end());
FindCloseChangeNotification(hit.value().handle);
thread->handles[index] = hit.value().handle = fileHandle;
hit.value().flags = flags;
thread->pathInfoForHandle.insert(fileHandle, pit.value());
thread->pathInfoForHandle.erase(pit);
}
}
// In addition, check on flags for sufficient notification attributes
if (hit != thread->handleForDir.end() && hit.value().flags >= flags) {
handle = hit.value();
// found a thread now insert...
DEBUG() << "Found a thread" << thread;
@ -426,14 +460,9 @@ QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths,
}
// no thread found, first create a handle
if (handle.handle == INVALID_HANDLE_VALUE || handle.flags != flags) {
if (handle.handle == INVALID_HANDLE_VALUE) {
DEBUG() << "No thread found";
// Volume and folder paths need a trailing slash for proper notification
// (e.g. "c:" -> "c:/").
const QString effectiveAbsolutePath =
isDir ? (absolutePath + QLatin1Char('/')) : absolutePath;
handle.handle = FindFirstChangeNotification((wchar_t*) QDir::toNativeSeparators(effectiveAbsolutePath).utf16(), false, flags);
handle.handle = createChangeNotification(absolutePath, flags);
handle.flags = flags;
if (handle.handle == INVALID_HANDLE_VALUE)
continue;

View File

@ -568,6 +568,10 @@ void tst_QFileSystemWatcher::watchFileAndItsDirectory()
QCOMPARE(fileChangedSpy.count(), 0);
QCOMPARE(dirChangedSpy.count(), 1);
// QTBUG-61792, removal should succeed (bug on Windows which uses one change
// notification per directory).
QVERIFY(watcher.removePath(testDir.absolutePath()));
QVERIFY(temporaryDir.rmdir(testDirName));
}