| /**************************************************************************** |
| ** |
| ** Copyright (C) 2011 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 <qdebug.h> |
| #include <qfileinfo.h> |
| #include <qstringlist.h> |
| #include <qset.h> |
| #include <qdatetime.h> |
| #include <qdir.h> |
| |
| QT_BEGIN_NAMESPACE |
| |
| void QWindowsFileSystemWatcherEngine::stop() |
| { |
| foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) |
| thread->stop(); |
| } |
| |
| QWindowsFileSystemWatcherEngine::QWindowsFileSystemWatcherEngine() |
| : QFileSystemWatcherEngine(false) |
| { |
| } |
| |
| QWindowsFileSystemWatcherEngine::~QWindowsFileSystemWatcherEngine() |
| { |
| if (threads.isEmpty()) |
| return; |
| |
| foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) { |
| thread->stop(); |
| thread->wait(); |
| delete thread; |
| } |
| } |
| |
| QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths, |
| QStringList *files, |
| QStringList *directories) |
| { |
| // qDebug()<<"Adding"<<paths.count()<<"to existing"<<(files->count() + directories->count())<<"watchers"; |
| QStringList p = paths; |
| QMutableListIterator<QString> it(p); |
| while (it.hasNext()) { |
| QString path = it.next(); |
| QString normalPath = path; |
| if ((normalPath.endsWith(QLatin1Char('/')) || normalPath.endsWith(QLatin1Char('\\'))) |
| #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"<<normalPath; |
| |
| const QString absolutePath = isDir ? fileInfo.absoluteFilePath() : fileInfo.absolutePath(); |
| const uint flags = isDir |
| ? (FILE_NOTIFY_CHANGE_DIR_NAME |
| | FILE_NOTIFY_CHANGE_FILE_NAME) |
| : (FILE_NOTIFY_CHANGE_DIR_NAME |
| | FILE_NOTIFY_CHANGE_FILE_NAME |
| | FILE_NOTIFY_CHANGE_ATTRIBUTES |
| | FILE_NOTIFY_CHANGE_SIZE |
| | FILE_NOTIFY_CHANGE_LAST_WRITE |
| | FILE_NOTIFY_CHANGE_SECURITY); |
| |
| QWindowsFileSystemWatcherEngine::PathInfo pathInfo; |
| pathInfo.absolutePath = absolutePath; |
| pathInfo.isDir = isDir; |
| pathInfo.path = path; |
| pathInfo = fileInfo; |
| |
| // Look for a thread |
| QWindowsFileSystemWatcherEngineThread *thread = 0; |
| QWindowsFileSystemWatcherEngine::Handle handle; |
| QList<QWindowsFileSystemWatcherEngineThread *>::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"<<thread; |
| |
| QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> &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"<<thread; |
| 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"<<thread<<"("<<(threads.count()+1)<<"threads)"; |
| 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"<<paths; |
| QStringList p = paths; |
| QMutableListIterator<QString> 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"<<normalPath; |
| QString absolutePath = fileInfo.absoluteFilePath(); |
| QList<QWindowsFileSystemWatcherEngineThread *>::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<QString, QWindowsFileSystemWatcherEngine::PathInfo> &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 "<<thread; |
| locker.unlock(); |
| 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<QWindowsFileSystemWatcherEngineThread *>::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<HANDLE> handlesCopy = handles; |
| locker.unlock(); |
| // qDebug() << "QWindowsFileSystemWatcherThread"<<this<<"waiting on" << handlesCopy.count() << "handles"; |
| DWORD r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE); |
| locker.relock(); |
| do { |
| if (r == WAIT_OBJECT_0) { |
| int m = msg; |
| msg = 0; |
| if (m == 'q') { |
| // qDebug() << "thread"<<this<<"told to quit"; |
| return; |
| } |
| if (m != '@') { |
| qDebug("QWindowsFileSystemWatcherEngine: unknown message '%c' send to thread", char(m)); |
| } |
| break; |
| } else if (r > 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"<<this<<"Acknowledged handle:"<<at<<handle; |
| if (!FindNextChangeNotification(handle)) { |
| qErrnoWarning("QFileSystemWatcher: FindNextChangeNotification failed!!"); |
| } |
| |
| QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> &h = pathInfoForHandle[handle]; |
| QMutableHashIterator<QString, QWindowsFileSystemWatcherEngine::PathInfo> it(h); |
| while (it.hasNext()) { |
| QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo>::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 |