/**************************************************************************** ** ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies). ** All rights reserved. ** Contact: Nokia Corporation (qt-info@nokia.com) ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** GNU Lesser General Public License Usage ** This file may be used under the terms of the GNU Lesser General Public ** License version 2.1 as published by the Free Software Foundation and ** appearing in the file LICENSE.LGPL included in the packaging of this ** file. Please review the following information to ensure the GNU Lesser ** General Public License version 2.1 requirements will be met: ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. ** ** In addition, as a special exception, Nokia gives you certain additional ** rights. These rights are described in the Nokia Qt LGPL Exception ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU General ** Public License version 3.0 as published by the Free Software Foundation ** and appearing in the file LICENSE.GPL included in the packaging of this ** file. Please review the following information to ensure the GNU General ** Public License version 3.0 requirements will be met: ** http://www.gnu.org/copyleft/gpl.html. ** ** Other Usage ** Alternatively, this file may be used in accordance with the terms and ** conditions contained in a signed written agreement between you and Nokia. ** ** ** ** ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qfilesystemwatcher.h" #include "qfilesystemwatcher_win_p.h" #ifndef QT_NO_FILESYSTEMWATCHER #include #include #include #include #include #include QT_BEGIN_NAMESPACE QWindowsFileSystemWatcherEngine::~QWindowsFileSystemWatcherEngine() { foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) { thread->stop(); thread->wait(); delete thread; } } QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths, QStringList *files, QStringList *directories) { // qDebug()<<"Adding"<count() + directories->count())<<"watchers"; QStringList p = paths; QMutableListIterator it(p); while (it.hasNext()) { QString path = it.next(); QString normalPath = path; if ((normalPath.endsWith(QLatin1Char('/')) && !normalPath.endsWith(QLatin1String(":/"))) || (normalPath.endsWith(QLatin1Char('\\')) && !normalPath.endsWith(QLatin1String(":\\"))) #ifdef Q_OS_WINCE && normalPath.size() > 1) #else ) #endif normalPath.chop(1); QFileInfo fileInfo(normalPath.toLower()); if (!fileInfo.exists()) continue; bool isDir = fileInfo.isDir(); if (isDir) { if (directories->contains(path)) continue; } else { if (files->contains(path)) continue; } // qDebug()<<"Looking for a thread/handle for"<::const_iterator jt, end; end = threads.constEnd(); for(jt = threads.constBegin(); jt != end; ++jt) { thread = *jt; QMutexLocker locker(&(thread->mutex)); handle = thread->handleForDir.value(absolutePath); if (handle.handle != INVALID_HANDLE_VALUE && handle.flags == flags) { // found a thread now insert... // qDebug()<<" Found a thread"< &h = thread->pathInfoForHandle[handle.handle]; if (!h.contains(fileInfo.absoluteFilePath())) { thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); if (isDir) directories->append(path); else files->append(path); } it.remove(); thread->wakeup(); break; } } // no thread found, first create a handle if (handle.handle == INVALID_HANDLE_VALUE || handle.flags != flags) { // qDebug()<<" 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.flags = flags; if (handle.handle == INVALID_HANDLE_VALUE) continue; // now look for a thread to insert bool found = false; foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) { QMutexLocker(&(thread->mutex)); if (thread->handles.count() < MAXIMUM_WAIT_OBJECTS) { // qDebug() << " Added handle" << handle.handle << "for" << absolutePath << "to watch" << fileInfo.absoluteFilePath(); // qDebug()<< " to existing thread"<handles.append(handle.handle); thread->handleForDir.insert(absolutePath, handle); thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); if (isDir) directories->append(path); else files->append(path); it.remove(); found = true; thread->wakeup(); break; } } if (!found) { QWindowsFileSystemWatcherEngineThread *thread = new QWindowsFileSystemWatcherEngineThread(); //qDebug()<<" ###Creating new thread"<handles.append(handle.handle); thread->handleForDir.insert(absolutePath, handle); thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); if (isDir) directories->append(path); else files->append(path); connect(thread, SIGNAL(fileChanged(QString,bool)), this, SIGNAL(fileChanged(QString,bool))); connect(thread, SIGNAL(directoryChanged(QString,bool)), this, SIGNAL(directoryChanged(QString,bool))); thread->msg = '@'; thread->start(); threads.append(thread); it.remove(); } } } return p; } QStringList QWindowsFileSystemWatcherEngine::removePaths(const QStringList &paths, QStringList *files, QStringList *directories) { // qDebug()<<"removePaths"< it(p); while (it.hasNext()) { QString path = it.next(); QString normalPath = path; if (normalPath.endsWith(QLatin1Char('/')) || normalPath.endsWith(QLatin1Char('\\'))) normalPath.chop(1); QFileInfo fileInfo(normalPath.toLower()); // qDebug()<<"removing"<::iterator jt, end; end = threads.end(); for(jt = threads.begin(); jt!= end; ++jt) { QWindowsFileSystemWatcherEngineThread *thread = *jt; if (*jt == 0) continue; QMutexLocker locker(&(thread->mutex)); QWindowsFileSystemWatcherEngine::Handle handle = thread->handleForDir.value(absolutePath); if (handle.handle == INVALID_HANDLE_VALUE) { // perhaps path is a file? absolutePath = fileInfo.absolutePath(); handle = thread->handleForDir.value(absolutePath); } if (handle.handle != INVALID_HANDLE_VALUE) { QHash &h = thread->pathInfoForHandle[handle.handle]; if (h.remove(fileInfo.absoluteFilePath())) { // ### files->removeAll(path); directories->removeAll(path); if (h.isEmpty()) { // qDebug() << "Closing handle" << handle.handle; FindCloseChangeNotification(handle.handle); // This one might generate a notification int indexOfHandle = thread->handles.indexOf(handle.handle); Q_ASSERT(indexOfHandle != -1); thread->handles.remove(indexOfHandle); thread->handleForDir.remove(absolutePath); // h is now invalid it.remove(); if (thread->handleForDir.isEmpty()) { // qDebug()<<"Stopping thread "<stop(); thread->wait(); locker.relock(); // We can't delete the thread until the mutex locker is // out of scope } } } // Found the file, go to next one break; } } } // Remove all threads that we stopped QList::iterator jt, end; end = threads.end(); for(jt = threads.begin(); jt != end; ++jt) { if (!(*jt)->isRunning()) { delete *jt; *jt = 0; } } threads.removeAll(0); return p; } /////////// // QWindowsFileSystemWatcherEngineThread /////////// QWindowsFileSystemWatcherEngineThread::QWindowsFileSystemWatcherEngineThread() : msg(0) { if (HANDLE h = CreateEvent(0, false, false, 0)) { handles.reserve(MAXIMUM_WAIT_OBJECTS); handles.append(h); } moveToThread(this); } QWindowsFileSystemWatcherEngineThread::~QWindowsFileSystemWatcherEngineThread() { CloseHandle(handles.at(0)); handles[0] = INVALID_HANDLE_VALUE; foreach (HANDLE h, handles) { if (h == INVALID_HANDLE_VALUE) continue; FindCloseChangeNotification(h); } } void QWindowsFileSystemWatcherEngineThread::run() { QMutexLocker locker(&mutex); forever { QVector handlesCopy = handles; locker.unlock(); // qDebug() << "QWindowsFileSystemWatcherThread"< WAIT_OBJECT_0 && r < WAIT_OBJECT_0 + uint(handlesCopy.count())) { int at = r - WAIT_OBJECT_0; Q_ASSERT(at < handlesCopy.count()); HANDLE handle = handlesCopy.at(at); // When removing a path, FindCloseChangeNotification might actually fire a notification // for some reason, so we must check if the handle exist in the handles vector if (handles.contains(handle)) { // qDebug()<<"thread"< &h = pathInfoForHandle[handle]; QMutableHashIterator it(h); while (it.hasNext()) { QHash::iterator x = it.next(); QString absolutePath = x.value().absolutePath; QFileInfo fileInfo(x.value().path); // qDebug() << "checking" << x.key(); if (!fileInfo.exists()) { // qDebug() << x.key() << "removed!"; if (x.value().isDir) emit directoryChanged(x.value().path, true); else emit fileChanged(x.value().path, true); h.erase(x); // close the notification handle if the directory has been removed if (h.isEmpty()) { // qDebug() << "Thread closing handle" << handle; FindCloseChangeNotification(handle); // This one might generate a notification int indexOfHandle = handles.indexOf(handle); Q_ASSERT(indexOfHandle != -1); handles.remove(indexOfHandle); handleForDir.remove(absolutePath); // h is now invalid } } else if (x.value().isDir) { // qDebug() << x.key() << "directory changed!"; emit directoryChanged(x.value().path, false); x.value() = fileInfo; } else if (x.value() != fileInfo) { // qDebug() << x.key() << "file changed!"; emit fileChanged(x.value().path, false); x.value() = fileInfo; } } } } else { // qErrnoWarning("QFileSystemWatcher: error while waiting for change notification"); break; // avoid endless loop } handlesCopy = handles; r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, 0); } while (r != WAIT_TIMEOUT); } } void QWindowsFileSystemWatcherEngineThread::stop() { msg = 'q'; SetEvent(handles.at(0)); } void QWindowsFileSystemWatcherEngineThread::wakeup() { msg = '@'; SetEvent(handles.at(0)); } QT_END_NAMESPACE #endif // QT_NO_FILESYSTEMWATCHER