author | eckhart.koppen@nokia.com |
Wed, 31 Mar 2010 11:06:36 +0300 | |
changeset 7 | f7bc934e204c |
parent 3 | 41300fa6a67c |
permissions | -rw-r--r-- |
0 | 1 |
/**************************************************************************** |
2 |
** |
|
7
f7bc934e204c
5cabc75a39ca2f064f70b40f72ed93c74c4dc19b
eckhart.koppen@nokia.com
parents:
3
diff
changeset
|
3 |
** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). |
0 | 4 |
** All rights reserved. |
5 |
** Contact: Nokia Corporation (qt-info@nokia.com) |
|
6 |
** |
|
7 |
** This file is part of the QtCore module of the Qt Toolkit. |
|
8 |
** |
|
9 |
** $QT_BEGIN_LICENSE:LGPL$ |
|
10 |
** No Commercial Usage |
|
11 |
** This file contains pre-release code and may not be distributed. |
|
12 |
** You may use this file in accordance with the terms and conditions |
|
13 |
** contained in the Technology Preview License Agreement accompanying |
|
14 |
** this package. |
|
15 |
** |
|
16 |
** GNU Lesser General Public License Usage |
|
17 |
** Alternatively, this file may be used under the terms of the GNU Lesser |
|
18 |
** General Public License version 2.1 as published by the Free Software |
|
19 |
** Foundation and appearing in the file LICENSE.LGPL included in the |
|
20 |
** packaging of this file. Please review the following information to |
|
21 |
** ensure the GNU Lesser General Public License version 2.1 requirements |
|
22 |
** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
|
23 |
** |
|
24 |
** In addition, as a special exception, Nokia gives you certain additional |
|
25 |
** rights. These rights are described in the Nokia Qt LGPL Exception |
|
26 |
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
|
27 |
** |
|
28 |
** If you have questions regarding the use of this file, please contact |
|
29 |
** Nokia at qt-info@nokia.com. |
|
30 |
** |
|
31 |
** |
|
32 |
** |
|
33 |
** |
|
34 |
** |
|
35 |
** |
|
36 |
** |
|
37 |
** |
|
38 |
** $QT_END_LICENSE$ |
|
39 |
** |
|
40 |
****************************************************************************/ |
|
41 |
||
42 |
#include "qfilesystemwatcher.h" |
|
43 |
#include "qfilesystemwatcher_win_p.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 |
||
54 |
QT_BEGIN_NAMESPACE |
|
55 |
||
56 |
void QWindowsFileSystemWatcherEngine::stop() |
|
57 |
{ |
|
58 |
foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) |
|
59 |
thread->stop(); |
|
60 |
} |
|
61 |
||
62 |
QWindowsFileSystemWatcherEngine::QWindowsFileSystemWatcherEngine() |
|
63 |
: QFileSystemWatcherEngine(false) |
|
64 |
{ |
|
65 |
} |
|
66 |
||
67 |
QWindowsFileSystemWatcherEngine::~QWindowsFileSystemWatcherEngine() |
|
68 |
{ |
|
69 |
if (threads.isEmpty()) |
|
70 |
return; |
|
71 |
||
72 |
foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) { |
|
73 |
thread->stop(); |
|
74 |
thread->wait(); |
|
75 |
delete thread; |
|
76 |
} |
|
77 |
} |
|
78 |
||
79 |
QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths, |
|
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(QLatin1Char('\\'))) |
|
90 |
#ifdef Q_OS_WINCE |
|
91 |
&& normalPath.size() > 1) |
|
92 |
#else |
|
93 |
) |
|
94 |
#endif |
|
95 |
normalPath.chop(1); |
|
96 |
QFileInfo fileInfo(normalPath.toLower()); |
|
97 |
if (!fileInfo.exists()) |
|
98 |
continue; |
|
99 |
||
100 |
bool isDir = fileInfo.isDir(); |
|
101 |
if (isDir) { |
|
102 |
if (directories->contains(path)) |
|
103 |
continue; |
|
104 |
} else { |
|
105 |
if (files->contains(path)) |
|
106 |
continue; |
|
107 |
} |
|
108 |
||
109 |
// qDebug()<<"Looking for a thread/handle for"<<normalPath; |
|
110 |
||
111 |
const QString absolutePath = isDir ? fileInfo.absoluteFilePath() : fileInfo.absolutePath(); |
|
112 |
const uint flags = isDir |
|
113 |
? (FILE_NOTIFY_CHANGE_DIR_NAME |
|
114 |
| FILE_NOTIFY_CHANGE_FILE_NAME) |
|
115 |
: (FILE_NOTIFY_CHANGE_DIR_NAME |
|
116 |
| FILE_NOTIFY_CHANGE_FILE_NAME |
|
117 |
| FILE_NOTIFY_CHANGE_ATTRIBUTES |
|
118 |
| FILE_NOTIFY_CHANGE_SIZE |
|
119 |
| FILE_NOTIFY_CHANGE_LAST_WRITE |
|
120 |
| FILE_NOTIFY_CHANGE_SECURITY); |
|
121 |
||
122 |
QWindowsFileSystemWatcherEngine::PathInfo pathInfo; |
|
123 |
pathInfo.absolutePath = absolutePath; |
|
124 |
pathInfo.isDir = isDir; |
|
125 |
pathInfo.path = path; |
|
126 |
pathInfo = fileInfo; |
|
127 |
||
128 |
// Look for a thread |
|
129 |
QWindowsFileSystemWatcherEngineThread *thread = 0; |
|
130 |
QWindowsFileSystemWatcherEngine::Handle handle; |
|
131 |
QList<QWindowsFileSystemWatcherEngineThread *>::const_iterator jt, end; |
|
132 |
end = threads.constEnd(); |
|
133 |
for(jt = threads.constBegin(); jt != end; ++jt) { |
|
134 |
thread = *jt; |
|
135 |
QMutexLocker locker(&(thread->mutex)); |
|
136 |
||
137 |
handle = thread->handleForDir.value(absolutePath); |
|
138 |
if (handle.handle != INVALID_HANDLE_VALUE && handle.flags == flags) { |
|
139 |
// found a thread now insert... |
|
140 |
// qDebug()<<" Found a thread"<<thread; |
|
141 |
||
142 |
QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> &h |
|
143 |
= thread->pathInfoForHandle[handle.handle]; |
|
144 |
if (!h.contains(fileInfo.absoluteFilePath())) { |
|
145 |
thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); |
|
146 |
if (isDir) |
|
147 |
directories->append(path); |
|
148 |
else |
|
149 |
files->append(path); |
|
150 |
} |
|
151 |
it.remove(); |
|
152 |
thread->wakeup(); |
|
153 |
break; |
|
154 |
} |
|
155 |
} |
|
156 |
||
157 |
// no thread found, first create a handle |
|
158 |
if (handle.handle == INVALID_HANDLE_VALUE || handle.flags != flags) { |
|
159 |
// qDebug()<<" No thread found"; |
|
160 |
// Volume and folder paths need a trailing slash for proper notification |
|
161 |
// (e.g. "c:" -> "c:/"). |
|
162 |
const QString effectiveAbsolutePath = |
|
163 |
isDir ? (absolutePath + QLatin1Char('/')) : absolutePath; |
|
164 |
||
165 |
handle.handle = FindFirstChangeNotification((wchar_t*) QDir::toNativeSeparators(effectiveAbsolutePath).utf16(), false, flags); |
|
166 |
handle.flags = flags; |
|
167 |
if (handle.handle == INVALID_HANDLE_VALUE) |
|
168 |
continue; |
|
169 |
||
170 |
// now look for a thread to insert |
|
171 |
bool found = false; |
|
172 |
foreach(QWindowsFileSystemWatcherEngineThread *thread, threads) { |
|
173 |
QMutexLocker(&(thread->mutex)); |
|
174 |
if (thread->handles.count() < MAXIMUM_WAIT_OBJECTS) { |
|
175 |
// qDebug() << " Added handle" << handle.handle << "for" << absolutePath << "to watch" << fileInfo.absoluteFilePath(); |
|
176 |
// qDebug()<< " to existing thread"<<thread; |
|
177 |
thread->handles.append(handle.handle); |
|
178 |
thread->handleForDir.insert(absolutePath, handle); |
|
179 |
||
180 |
thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); |
|
181 |
if (isDir) |
|
182 |
directories->append(path); |
|
183 |
else |
|
184 |
files->append(path); |
|
185 |
||
186 |
it.remove(); |
|
187 |
found = true; |
|
188 |
thread->wakeup(); |
|
189 |
break; |
|
190 |
} |
|
191 |
} |
|
192 |
if (!found) { |
|
193 |
QWindowsFileSystemWatcherEngineThread *thread = new QWindowsFileSystemWatcherEngineThread(); |
|
194 |
//qDebug()<<" ###Creating new thread"<<thread<<"("<<(threads.count()+1)<<"threads)"; |
|
195 |
thread->handles.append(handle.handle); |
|
196 |
thread->handleForDir.insert(absolutePath, handle); |
|
197 |
||
198 |
thread->pathInfoForHandle[handle.handle].insert(fileInfo.absoluteFilePath(), pathInfo); |
|
199 |
if (isDir) |
|
200 |
directories->append(path); |
|
201 |
else |
|
202 |
files->append(path); |
|
203 |
||
3
41300fa6a67c
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
0
diff
changeset
|
204 |
connect(thread, SIGNAL(fileChanged(QString,bool)), |
41300fa6a67c
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
0
diff
changeset
|
205 |
this, SIGNAL(fileChanged(QString,bool))); |
41300fa6a67c
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
0
diff
changeset
|
206 |
connect(thread, SIGNAL(directoryChanged(QString,bool)), |
41300fa6a67c
Revision: 201003
Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
parents:
0
diff
changeset
|
207 |
this, SIGNAL(directoryChanged(QString,bool))); |
0 | 208 |
|
209 |
thread->msg = '@'; |
|
210 |
thread->start(); |
|
211 |
threads.append(thread); |
|
212 |
it.remove(); |
|
213 |
} |
|
214 |
} |
|
215 |
} |
|
216 |
return p; |
|
217 |
} |
|
218 |
||
219 |
QStringList QWindowsFileSystemWatcherEngine::removePaths(const QStringList &paths, |
|
220 |
QStringList *files, |
|
221 |
QStringList *directories) |
|
222 |
{ |
|
223 |
// qDebug()<<"removePaths"<<paths; |
|
224 |
QStringList p = paths; |
|
225 |
QMutableListIterator<QString> it(p); |
|
226 |
while (it.hasNext()) { |
|
227 |
QString path = it.next(); |
|
228 |
QString normalPath = path; |
|
229 |
if (normalPath.endsWith(QLatin1Char('/')) || normalPath.endsWith(QLatin1Char('\\'))) |
|
230 |
normalPath.chop(1); |
|
231 |
QFileInfo fileInfo(normalPath.toLower()); |
|
232 |
// qDebug()<<"removing"<<normalPath; |
|
233 |
QString absolutePath = fileInfo.absoluteFilePath(); |
|
234 |
QList<QWindowsFileSystemWatcherEngineThread *>::iterator jt, end; |
|
235 |
end = threads.end(); |
|
236 |
for(jt = threads.begin(); jt!= end; ++jt) { |
|
237 |
QWindowsFileSystemWatcherEngineThread *thread = *jt; |
|
238 |
if (*jt == 0) |
|
239 |
continue; |
|
240 |
||
241 |
QMutexLocker locker(&(thread->mutex)); |
|
242 |
||
243 |
QWindowsFileSystemWatcherEngine::Handle handle = thread->handleForDir.value(absolutePath); |
|
244 |
if (handle.handle == INVALID_HANDLE_VALUE) { |
|
245 |
// perhaps path is a file? |
|
246 |
absolutePath = fileInfo.absolutePath(); |
|
247 |
handle = thread->handleForDir.value(absolutePath); |
|
248 |
} |
|
249 |
if (handle.handle != INVALID_HANDLE_VALUE) { |
|
250 |
QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> &h = |
|
251 |
thread->pathInfoForHandle[handle.handle]; |
|
252 |
if (h.remove(fileInfo.absoluteFilePath())) { |
|
253 |
// ### |
|
254 |
files->removeAll(path); |
|
255 |
directories->removeAll(path); |
|
256 |
||
257 |
if (h.isEmpty()) { |
|
258 |
// qDebug() << "Closing handle" << handle.handle; |
|
259 |
FindCloseChangeNotification(handle.handle); // This one might generate a notification |
|
260 |
||
261 |
int indexOfHandle = thread->handles.indexOf(handle.handle); |
|
262 |
Q_ASSERT(indexOfHandle != -1); |
|
263 |
thread->handles.remove(indexOfHandle); |
|
264 |
||
265 |
thread->handleForDir.remove(absolutePath); |
|
266 |
// h is now invalid |
|
267 |
||
268 |
it.remove(); |
|
269 |
||
270 |
if (thread->handleForDir.isEmpty()) { |
|
271 |
// qDebug()<<"Stopping thread "<<thread; |
|
272 |
locker.unlock(); |
|
273 |
thread->stop(); |
|
274 |
thread->wait(); |
|
275 |
locker.relock(); |
|
276 |
// We can't delete the thread until the mutex locker is |
|
277 |
// out of scope |
|
278 |
} |
|
279 |
} |
|
280 |
} |
|
281 |
// Found the file, go to next one |
|
282 |
break; |
|
283 |
} |
|
284 |
} |
|
285 |
} |
|
286 |
||
287 |
// Remove all threads that we stopped |
|
288 |
QList<QWindowsFileSystemWatcherEngineThread *>::iterator jt, end; |
|
289 |
end = threads.end(); |
|
290 |
for(jt = threads.begin(); jt != end; ++jt) { |
|
291 |
if (!(*jt)->isRunning()) { |
|
292 |
delete *jt; |
|
293 |
*jt = 0; |
|
294 |
} |
|
295 |
} |
|
296 |
||
297 |
threads.removeAll(0); |
|
298 |
return p; |
|
299 |
} |
|
300 |
||
301 |
/////////// |
|
302 |
// QWindowsFileSystemWatcherEngineThread |
|
303 |
/////////// |
|
304 |
||
305 |
QWindowsFileSystemWatcherEngineThread::QWindowsFileSystemWatcherEngineThread() |
|
306 |
: msg(0) |
|
307 |
{ |
|
308 |
if (HANDLE h = CreateEvent(0, false, false, 0)) { |
|
309 |
handles.reserve(MAXIMUM_WAIT_OBJECTS); |
|
310 |
handles.append(h); |
|
311 |
} |
|
312 |
moveToThread(this); |
|
313 |
} |
|
314 |
||
315 |
||
316 |
QWindowsFileSystemWatcherEngineThread::~QWindowsFileSystemWatcherEngineThread() |
|
317 |
{ |
|
318 |
CloseHandle(handles.at(0)); |
|
319 |
handles[0] = INVALID_HANDLE_VALUE; |
|
320 |
||
321 |
foreach (HANDLE h, handles) { |
|
322 |
if (h == INVALID_HANDLE_VALUE) |
|
323 |
continue; |
|
324 |
FindCloseChangeNotification(h); |
|
325 |
} |
|
326 |
} |
|
327 |
||
328 |
void QWindowsFileSystemWatcherEngineThread::run() |
|
329 |
{ |
|
330 |
QMutexLocker locker(&mutex); |
|
331 |
forever { |
|
332 |
QVector<HANDLE> handlesCopy = handles; |
|
333 |
locker.unlock(); |
|
334 |
// qDebug() << "QWindowsFileSystemWatcherThread"<<this<<"waiting on" << handlesCopy.count() << "handles"; |
|
335 |
DWORD r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE); |
|
336 |
locker.relock(); |
|
337 |
do { |
|
338 |
if (r == WAIT_OBJECT_0) { |
|
339 |
int m = msg; |
|
340 |
msg = 0; |
|
341 |
if (m == 'q') { |
|
342 |
// qDebug() << "thread"<<this<<"told to quit"; |
|
343 |
return; |
|
344 |
} |
|
345 |
if (m != '@') { |
|
346 |
qDebug("QWindowsFileSystemWatcherEngine: unknown message '%c' send to thread", char(m)); |
|
347 |
} |
|
348 |
break; |
|
349 |
} else if (r > WAIT_OBJECT_0 && r < WAIT_OBJECT_0 + uint(handlesCopy.count())) { |
|
350 |
int at = r - WAIT_OBJECT_0; |
|
351 |
Q_ASSERT(at < handlesCopy.count()); |
|
352 |
HANDLE handle = handlesCopy.at(at); |
|
353 |
||
354 |
// When removing a path, FindCloseChangeNotification might actually fire a notification |
|
355 |
// for some reason, so we must check if the handle exist in the handles vector |
|
356 |
if (handles.contains(handle)) { |
|
357 |
// qDebug()<<"thread"<<this<<"Acknowledged handle:"<<at<<handle; |
|
358 |
if (!FindNextChangeNotification(handle)) { |
|
359 |
qErrnoWarning("QFileSystemWatcher: FindNextChangeNotification failed!!"); |
|
360 |
} |
|
361 |
||
362 |
QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo> &h = pathInfoForHandle[handle]; |
|
363 |
QMutableHashIterator<QString, QWindowsFileSystemWatcherEngine::PathInfo> it(h); |
|
364 |
while (it.hasNext()) { |
|
365 |
QHash<QString, QWindowsFileSystemWatcherEngine::PathInfo>::iterator x = it.next(); |
|
366 |
QString absolutePath = x.value().absolutePath; |
|
367 |
QFileInfo fileInfo(x.value().path); |
|
368 |
// qDebug() << "checking" << x.key(); |
|
369 |
if (!fileInfo.exists()) { |
|
370 |
// qDebug() << x.key() << "removed!"; |
|
371 |
if (x.value().isDir) |
|
372 |
emit directoryChanged(x.value().path, true); |
|
373 |
else |
|
374 |
emit fileChanged(x.value().path, true); |
|
375 |
h.erase(x); |
|
376 |
||
377 |
// close the notification handle if the directory has been removed |
|
378 |
if (h.isEmpty()) { |
|
379 |
// qDebug() << "Thread closing handle" << handle; |
|
380 |
FindCloseChangeNotification(handle); // This one might generate a notification |
|
381 |
||
382 |
int indexOfHandle = handles.indexOf(handle); |
|
383 |
Q_ASSERT(indexOfHandle != -1); |
|
384 |
handles.remove(indexOfHandle); |
|
385 |
||
386 |
handleForDir.remove(absolutePath); |
|
387 |
// h is now invalid |
|
388 |
} |
|
389 |
} else if (x.value().isDir) { |
|
390 |
// qDebug() << x.key() << "directory changed!"; |
|
391 |
emit directoryChanged(x.value().path, false); |
|
392 |
x.value() = fileInfo; |
|
393 |
} else if (x.value() != fileInfo) { |
|
394 |
// qDebug() << x.key() << "file changed!"; |
|
395 |
emit fileChanged(x.value().path, false); |
|
396 |
x.value() = fileInfo; |
|
397 |
} |
|
398 |
} |
|
399 |
} |
|
400 |
} else { |
|
401 |
// qErrnoWarning("QFileSystemWatcher: error while waiting for change notification"); |
|
402 |
break; // avoid endless loop |
|
403 |
} |
|
404 |
handlesCopy = handles; |
|
405 |
r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, 0); |
|
406 |
} while (r != WAIT_TIMEOUT); |
|
407 |
} |
|
408 |
} |
|
409 |
||
410 |
||
411 |
void QWindowsFileSystemWatcherEngineThread::stop() |
|
412 |
{ |
|
413 |
msg = 'q'; |
|
414 |
SetEvent(handles.at(0)); |
|
415 |
} |
|
416 |
||
417 |
void QWindowsFileSystemWatcherEngineThread::wakeup() |
|
418 |
{ |
|
419 |
msg = '@'; |
|
420 |
SetEvent(handles.at(0)); |
|
421 |
} |
|
422 |
||
423 |
QT_END_NAMESPACE |
|
424 |
#endif // QT_NO_FILESYSTEMWATCHER |