Qt 4.8
qfilesystemwatcher_win.cpp
Go to the documentation of this file.
1 /****************************************************************************
2 **
3 ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies).
4 ** Contact: http://www.qt-project.org/legal
5 **
6 ** This file is part of the QtCore module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and Digia. For licensing terms and
14 ** conditions see http://qt.digia.com/licensing. For further information
15 ** use the contact form at http://qt.digia.com/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 2.1 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 2.1 requirements
23 ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
24 **
25 ** In addition, as a special exception, Digia gives you certain additional
26 ** rights. These rights are described in the Digia Qt LGPL Exception
27 ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
28 **
29 ** GNU General Public License Usage
30 ** Alternatively, this file may be used under the terms of the GNU
31 ** General Public License version 3.0 as published by the Free Software
32 ** Foundation and appearing in the file LICENSE.GPL included in the
33 ** packaging of this file. Please review the following information to
34 ** ensure the GNU General Public License version 3.0 requirements will be
35 ** met: http://www.gnu.org/copyleft/gpl.html.
36 **
37 **
38 ** $QT_END_LICENSE$
39 **
40 ****************************************************************************/
41 
42 #include "qfilesystemwatcher.h"
44 
45 #ifndef QT_NO_FILESYSTEMWATCHER
46 
47 #include <qdebug.h>
48 #include <qfileinfo.h>
49 #include <qstringlist.h>
50 #include <qset.h>
51 #include <qdatetime.h>
52 #include <qdir.h>
53 
55 
57 {
59  thread->stop();
60 }
61 
64 {
65 }
66 
68 {
69  if (threads.isEmpty())
70  return;
71 
73  thread->stop();
74  thread->wait();
75  delete thread;
76  }
77 }
78 
80  QStringList *files,
81  QStringList *directories)
82 {
83  // qDebug()<<"Adding"<<paths.count()<<"to existing"<<(files->count() + directories->count())<<"watchers";
84  QStringList p = paths;
85  QMutableListIterator<QString> it(p);
86  while (it.hasNext()) {
87  QString path = it.next();
88  QString normalPath = path;
89  if ((normalPath.endsWith(QLatin1Char('/')) && !normalPath.endsWith(QLatin1String(":/")))
90  || (normalPath.endsWith(QLatin1Char('\\')) && !normalPath.endsWith(QLatin1String(":\\")))
91 #ifdef Q_OS_WINCE
92  && normalPath.size() > 1)
93 #else
94  )
95 #endif
96  normalPath.chop(1);
97  QFileInfo fileInfo(normalPath.toLower());
98  if (!fileInfo.exists())
99  continue;
100 
101  bool isDir = fileInfo.isDir();
102  if (isDir) {
103  if (directories->contains(path))
104  continue;
105  } else {
106  if (files->contains(path))
107  continue;
108  }
109 
110  // qDebug()<<"Looking for a thread/handle for"<<normalPath;
111 
112  const QString absolutePath = isDir ? fileInfo.absoluteFilePath() : fileInfo.absolutePath();
113  const uint flags = isDir
114  ? (FILE_NOTIFY_CHANGE_DIR_NAME
115  | FILE_NOTIFY_CHANGE_FILE_NAME)
116  : (FILE_NOTIFY_CHANGE_DIR_NAME
117  | FILE_NOTIFY_CHANGE_FILE_NAME
118  | FILE_NOTIFY_CHANGE_ATTRIBUTES
119  | FILE_NOTIFY_CHANGE_SIZE
120  | FILE_NOTIFY_CHANGE_LAST_WRITE
121  | FILE_NOTIFY_CHANGE_SECURITY);
122 
124  pathInfo.absolutePath = absolutePath;
125  pathInfo.isDir = isDir;
126  pathInfo.path = path;
127  pathInfo = fileInfo;
128 
129  // Look for a thread
133  end = threads.constEnd();
134  for(jt = threads.constBegin(); jt != end; ++jt) {
135  thread = *jt;
136  QMutexLocker locker(&(thread->mutex));
137 
138  handle = thread->handleForDir.value(absolutePath);
139  if (handle.handle != INVALID_HANDLE_VALUE && handle.flags == flags) {
140  // found a thread now insert...
141  // qDebug()<<" Found a thread"<<thread;
142 
144  = thread->pathInfoForHandle[handle.handle];
145  if (!h.contains(fileInfo.absoluteFilePath())) {
146  thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo);
147  if (isDir)
148  directories->append(path);
149  else
150  files->append(path);
151  }
152  it.remove();
153  thread->wakeup();
154  break;
155  }
156  }
157 
158  // no thread found, first create a handle
159  if (handle.handle == INVALID_HANDLE_VALUE || handle.flags != flags) {
160  // qDebug()<<" No thread found";
161  // Volume and folder paths need a trailing slash for proper notification
162  // (e.g. "c:" -> "c:/").
163  const QString effectiveAbsolutePath =
164  isDir ? (absolutePath + QLatin1Char('/')) : absolutePath;
165 
166  handle.handle = FindFirstChangeNotification((wchar_t*) QDir::toNativeSeparators(effectiveAbsolutePath).utf16(), false, flags);
167  handle.flags = flags;
168  if (handle.handle == INVALID_HANDLE_VALUE)
169  continue;
170 
171  // now look for a thread to insert
172  bool found = false;
174  QMutexLocker(&(thread->mutex));
175  if (thread->handles.count() < MAXIMUM_WAIT_OBJECTS) {
176  // qDebug() << " Added handle" << handle.handle << "for" << absolutePath << "to watch" << fileInfo.absoluteFilePath();
177  // qDebug()<< " to existing thread"<<thread;
178  thread->handles.append(handle.handle);
179  thread->handleForDir.insert(absolutePath, handle);
180 
181  thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo);
182  if (isDir)
183  directories->append(path);
184  else
185  files->append(path);
186 
187  it.remove();
188  found = true;
189  thread->wakeup();
190  break;
191  }
192  }
193  if (!found) {
195  //qDebug()<<" ###Creating new thread"<<thread<<"("<<(threads.count()+1)<<"threads)";
196  thread->handles.append(handle.handle);
197  thread->handleForDir.insert(absolutePath, handle);
198 
199  thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo);
200  if (isDir)
201  directories->append(path);
202  else
203  files->append(path);
204 
205  connect(thread, SIGNAL(fileChanged(QString,bool)),
206  this, SIGNAL(fileChanged(QString,bool)));
207  connect(thread, SIGNAL(directoryChanged(QString,bool)),
208  this, SIGNAL(directoryChanged(QString,bool)));
209 
210  thread->msg = '@';
211  thread->start();
212  threads.append(thread);
213  it.remove();
214  }
215  }
216  }
217  return p;
218 }
219 
221  QStringList *files,
222  QStringList *directories)
223 {
224  // qDebug()<<"removePaths"<<paths;
225  QStringList p = paths;
226  QMutableListIterator<QString> it(p);
227  while (it.hasNext()) {
228  QString path = it.next();
229  QString normalPath = path;
230  if (normalPath.endsWith(QLatin1Char('/')) || normalPath.endsWith(QLatin1Char('\\')))
231  normalPath.chop(1);
232  QFileInfo fileInfo(normalPath.toLower());
233  // qDebug()<<"removing"<<normalPath;
234  QString absolutePath = fileInfo.absoluteFilePath();
236  end = threads.end();
237  for(jt = threads.begin(); jt!= end; ++jt) {
239  if (*jt == 0)
240  continue;
241 
242  QMutexLocker locker(&(thread->mutex));
243 
244  QWindowsFileSystemWatcherEngine::Handle handle = thread->handleForDir.value(absolutePath);
245  if (handle.handle == INVALID_HANDLE_VALUE) {
246  // perhaps path is a file?
247  absolutePath = fileInfo.absolutePath();
248  handle = thread->handleForDir.value(absolutePath);
249  }
250  if (handle.handle != INVALID_HANDLE_VALUE) {
252  thread->pathInfoForHandle[handle.handle];
253  if (h.remove(fileInfo.absoluteFilePath())) {
254  // ###
255  files->removeAll(path);
256  directories->removeAll(path);
257 
258  if (h.isEmpty()) {
259  // qDebug() << "Closing handle" << handle.handle;
260  FindCloseChangeNotification(handle.handle); // This one might generate a notification
261 
262  int indexOfHandle = thread->handles.indexOf(handle.handle);
263  Q_ASSERT(indexOfHandle != -1);
264  thread->handles.remove(indexOfHandle);
265 
266  thread->handleForDir.remove(absolutePath);
267  // h is now invalid
268 
269  it.remove();
270 
271  if (thread->handleForDir.isEmpty()) {
272  // qDebug()<<"Stopping thread "<<thread;
273  locker.unlock();
274  thread->stop();
275  thread->wait();
276  locker.relock();
277  // We can't delete the thread until the mutex locker is
278  // out of scope
279  }
280  }
281  }
282  // Found the file, go to next one
283  break;
284  }
285  }
286  }
287 
288  // Remove all threads that we stopped
290  end = threads.end();
291  for(jt = threads.begin(); jt != end; ++jt) {
292  if (!(*jt)->isRunning()) {
293  delete *jt;
294  *jt = 0;
295  }
296  }
297 
298  threads.removeAll(0);
299  return p;
300 }
301 
303 // QWindowsFileSystemWatcherEngineThread
305 
307  : msg(0)
308 {
309  if (HANDLE h = CreateEvent(0, false, false, 0)) {
310  handles.reserve(MAXIMUM_WAIT_OBJECTS);
311  handles.append(h);
312  }
313  moveToThread(this);
314 }
315 
316 
318 {
319  CloseHandle(handles.at(0));
320  handles[0] = INVALID_HANDLE_VALUE;
321 
322  foreach (HANDLE h, handles) {
323  if (h == INVALID_HANDLE_VALUE)
324  continue;
325  FindCloseChangeNotification(h);
326  }
327 }
328 
330 {
331  QMutexLocker locker(&mutex);
332  forever {
333  QVector<HANDLE> handlesCopy = handles;
334  locker.unlock();
335  // qDebug() << "QWindowsFileSystemWatcherThread"<<this<<"waiting on" << handlesCopy.count() << "handles";
336  DWORD r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE);
337  locker.relock();
338  do {
339  if (r == WAIT_OBJECT_0) {
340  int m = msg;
341  msg = 0;
342  if (m == 'q') {
343  // qDebug() << "thread"<<this<<"told to quit";
344  return;
345  }
346  if (m != '@') {
347  qDebug("QWindowsFileSystemWatcherEngine: unknown message '%c' send to thread", char(m));
348  }
349  break;
350  } else if (r > WAIT_OBJECT_0 && r < WAIT_OBJECT_0 + uint(handlesCopy.count())) {
351  int at = r - WAIT_OBJECT_0;
352  Q_ASSERT(at < handlesCopy.count());
353  HANDLE handle = handlesCopy.at(at);
354 
355  // When removing a path, FindCloseChangeNotification might actually fire a notification
356  // for some reason, so we must check if the handle exist in the handles vector
357  if (handles.contains(handle)) {
358  // qDebug()<<"thread"<<this<<"Acknowledged handle:"<<at<<handle;
359  if (!FindNextChangeNotification(handle)) {
360  qErrnoWarning("QFileSystemWatcher: FindNextChangeNotification failed!!");
361  }
362 
364  QMutableHashIterator<QString, QWindowsFileSystemWatcherEngine::PathInfo> it(h);
365  while (it.hasNext()) {
367  QString absolutePath = x.value().absolutePath;
368  QFileInfo fileInfo(x.value().path);
369  // qDebug() << "checking" << x.key();
370  if (!fileInfo.exists()) {
371  // qDebug() << x.key() << "removed!";
372  if (x.value().isDir)
373  emit directoryChanged(x.value().path, true);
374  else
375  emit fileChanged(x.value().path, true);
376  h.erase(x);
377 
378  // close the notification handle if the directory has been removed
379  if (h.isEmpty()) {
380  // qDebug() << "Thread closing handle" << handle;
381  FindCloseChangeNotification(handle); // This one might generate a notification
382 
383  int indexOfHandle = handles.indexOf(handle);
384  Q_ASSERT(indexOfHandle != -1);
385  handles.remove(indexOfHandle);
386 
387  handleForDir.remove(absolutePath);
388  // h is now invalid
389  }
390  } else if (x.value().isDir) {
391  // qDebug() << x.key() << "directory changed!";
392  emit directoryChanged(x.value().path, false);
393  x.value() = fileInfo;
394  } else if (x.value() != fileInfo) {
395  // qDebug() << x.key() << "file changed!";
396  emit fileChanged(x.value().path, false);
397  x.value() = fileInfo;
398  }
399  }
400  }
401  } else {
402  // qErrnoWarning("QFileSystemWatcher: error while waiting for change notification");
403  break; // avoid endless loop
404  }
405  handlesCopy = handles;
406  r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, 0);
407  } while (r != WAIT_TIMEOUT);
408  }
409 }
410 
411 
413 {
414  msg = 'q';
415  SetEvent(handles.at(0));
416 }
417 
419 {
420  msg = '@';
421  SetEvent(handles.at(0));
422 }
423 
425 #endif // QT_NO_FILESYSTEMWATCHER
QList< QWindowsFileSystemWatcherEngineThread * > threads
void fileChanged(const QString &path, bool removed)
#define QT_END_NAMESPACE
This macro expands to.
Definition: qglobal.h:90
int remove(const Key &key)
Removes all the items that have the key from the hash.
Definition: qhash.h:784
void remove(int i)
Removes the element at index position i.
Definition: qvector.h:374
#define it(className, varName)
int count(const T &t) const
Returns the number of occurrences of value in the vector.
Definition: qvector.h:742
#define at(className, varName)
void chop(int n)
Removes n characters from the end of the string.
Definition: qstring.cpp:4623
void unlock()
Unlocks this mutex locker.
Definition: qmutex.h:117
iterator begin()
Returns an STL-style iterator pointing to the first item in the list.
Definition: qlist.h:267
const_iterator constBegin() const
Returns a const STL-style iterator pointing to the first item in the list.
Definition: qlist.h:269
The QList::const_iterator class provides an STL-style const iterator for QList and QQueue...
Definition: qlist.h:228
QLatin1String(DBUS_INTERFACE_DBUS))) Q_GLOBAL_STATIC_WITH_ARGS(QString
The QString class provides a Unicode character string.
Definition: qstring.h:83
#define Q_ASSERT(cond)
Definition: qglobal.h:1823
#define WAIT_OBJECT_0
bool contains(const Key &key) const
Returns true if the hash contains an item with the key; otherwise returns false.
Definition: qhash.h:872
const T value(const Key &key) const
Returns the value associated with the key.
Definition: qhash.h:606
QHash< QString, QWindowsFileSystemWatcherEngine::Handle > handleForDir
bool isEmpty() const
Returns true if the list contains no items; otherwise returns false.
Definition: qlist.h:152
iterator insert(const Key &key, const T &value)
Inserts a new item with the key and a value of value.
Definition: qhash.h:753
void relock()
Relocks an unlocked mutex locker.
Definition: qmutex.h:125
Q_CORE_EXPORT void qDebug(const char *,...)
#define SIGNAL(a)
Definition: qobjectdefs.h:227
void append(const T &t)
Inserts value at the end of the list.
Definition: qlist.h:507
#define QT_BEGIN_NAMESPACE
This macro expands to.
Definition: qglobal.h:89
bool isDir() const
Returns true if this object points to a directory or to a symbolic link to a directory; otherwise ret...
Definition: qfileinfo.cpp:990
int size() const
Returns the number of characters in this string.
Definition: qstring.h:102
static bool connect(const QObject *sender, const char *signal, const QObject *receiver, const char *member, Qt::ConnectionType=Qt::AutoConnection)
Creates a connection of the given type from the signal in the sender object to the method in the rece...
Definition: qobject.cpp:2580
iterator end()
Returns an STL-style iterator pointing to the imaginary item after the last item in the list...
Definition: qlist.h:270
#define emit
Definition: qobjectdefs.h:76
The QStringList class provides a list of strings.
Definition: qstringlist.h:66
bool isEmpty() const
Returns true if the hash contains no items; otherwise returns false.
Definition: qhash.h:297
void append(const T &t)
Inserts value at the end of the vector.
Definition: qvector.h:573
unsigned int uint
Definition: qglobal.h:996
QBool contains(const QString &str, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the list contains the string str; otherwise returns false.
Definition: qstringlist.h:172
void moveToThread(QThread *thread)
Changes the thread affinity for this object and its children.
Definition: qobject.cpp:1458
QStringList removePaths(const QStringList &paths, QStringList *files, QStringList *directories)
void * HANDLE
Definition: qnamespace.h:1671
int indexOf(const T &t, int from=0) const
Returns the index position of the first occurrence of value in the vector, searching forward from ind...
Definition: qvector.h:698
void directoryChanged(const QString &path, bool removed)
const T & at(int i) const
Returns the item at index position i in the vector.
Definition: qvector.h:350
The QList::iterator class provides an STL-style non-const iterator for QList and QQueue.
Definition: qlist.h:181
void fileChanged(const QString &path, bool removed)
The QMutexLocker class is a convenience class that simplifies locking and unlocking mutexes...
Definition: qmutex.h:101
QHash< HANDLE, QHash< QString, QWindowsFileSystemWatcherEngine::PathInfo > > pathInfoForHandle
void start(Priority=InheritPriority)
Begins execution of the thread by calling run().
bool wait(unsigned long time=ULONG_MAX)
Blocks the thread until either of these conditions is met:
QString toLower() const Q_REQUIRED_RESULT
Returns a lowercase copy of the string.
Definition: qstring.cpp:5389
void run()
The starting point for the thread.
bool contains(const T &t) const
Returns true if the vector contains an occurrence of value; otherwise returns false.
Definition: qvector.h:731
void directoryChanged(const QString &path, bool removed)
const T * constData() const
Returns a const pointer to the data stored in the vector.
Definition: qvector.h:154
void reserve(int size)
Attempts to allocate memory for at least size elements.
Definition: qvector.h:339
QThread * thread() const
Returns the thread in which the object lives.
Definition: qobject.cpp:1419
QStringList addPaths(const QStringList &paths, QStringList *files, QStringList *directories)
bool endsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string ends with s; otherwise returns false.
Definition: qstring.cpp:3796
The QFileInfo class provides system-independent file information.
Definition: qfileinfo.h:60
static const KeyPair *const end
static QString toNativeSeparators(const QString &pathName)
Returns pathName with the &#39;/&#39; separators converted to separators that are appropriate for the underly...
Definition: qdir.cpp:812
iterator erase(iterator it)
Removes the (key, value) pair associated with the iterator pos from the hash, and returns an iterator...
Definition: qhash.h:827
The QLatin1Char class provides an 8-bit ASCII/Latin-1 character.
Definition: qchar.h:55
const_iterator constEnd() const
Returns a const STL-style iterator pointing to the imaginary item after the last item in the list...
Definition: qlist.h:272
void qErrnoWarning(const char *msg,...)
Definition: qglobal.cpp:2954
int removeAll(const T &t)
Removes all occurrences of value in the list and returns the number of entries removed.
Definition: qlist.h:770
#define forever
This macro is provided for convenience for writing infinite loops.
Definition: qglobal.h:2452