~gabriel1984sibiu/minitube/qt5.6

« back to all changes in this revision

Viewing changes to src/corelib/io/qfilesystemwatcher_win.cpp

  • Committer: Grevutiu Gabriel
  • Date: 2017-06-13 08:43:17 UTC
  • Revision ID: gabriel1984sibiu@gmail.com-20170613084317-ek0zqe0u9g3ocvi8
OriginalĀ upstreamĀ code

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/****************************************************************************
 
2
**
 
3
** Copyright (C) 2016 The Qt Company Ltd.
 
4
** Contact: https://www.qt.io/licensing/
 
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 The Qt Company. For licensing terms
 
14
** and conditions see https://www.qt.io/terms-conditions. For further
 
15
** information use the contact form at https://www.qt.io/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 3 as published by the Free Software
 
20
** Foundation and appearing in the file LICENSE.LGPL3 included in the
 
21
** packaging of this file. Please review the following information to
 
22
** ensure the GNU Lesser General Public License version 3 requirements
 
23
** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
 
24
**
 
25
** GNU General Public License Usage
 
26
** Alternatively, this file may be used under the terms of the GNU
 
27
** General Public License version 2.0 or (at your option) the GNU General
 
28
** Public license version 3 or any later version approved by the KDE Free
 
29
** Qt Foundation. The licenses are as published by the Free Software
 
30
** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
 
31
** included in the packaging of this file. Please review the following
 
32
** information to ensure the GNU General Public License requirements will
 
33
** be met: https://www.gnu.org/licenses/gpl-2.0.html and
 
34
** https://www.gnu.org/licenses/gpl-3.0.html.
 
35
**
 
36
** $QT_END_LICENSE$
 
37
**
 
38
****************************************************************************/
 
39
 
 
40
#include "qfilesystemwatcher.h"
 
41
#include "qfilesystemwatcher_win_p.h"
 
42
 
 
43
#ifndef QT_NO_FILESYSTEMWATCHER
 
44
 
 
45
#include <qdebug.h>
 
46
#include <qfileinfo.h>
 
47
#include <qstringlist.h>
 
48
#include <qset.h>
 
49
#include <qdatetime.h>
 
50
#include <qdir.h>
 
51
#include <qtextstream.h>
 
52
 
 
53
#include <qt_windows.h>
 
54
 
 
55
QT_BEGIN_NAMESPACE
 
56
 
 
57
// #define WINQFSW_DEBUG
 
58
#ifdef WINQFSW_DEBUG
 
59
#  define DEBUG qDebug
 
60
#else
 
61
#  define DEBUG if (false) qDebug
 
62
#endif
 
63
 
 
64
QWindowsFileSystemWatcherEngine::Handle::Handle()
 
65
    : handle(INVALID_HANDLE_VALUE), flags(0u)
 
66
{
 
67
}
 
68
 
 
69
QWindowsFileSystemWatcherEngine::~QWindowsFileSystemWatcherEngine()
 
70
{
 
71
    for (auto *thread : qAsConst(threads))
 
72
        thread->stop();
 
73
    for (auto *thread : qAsConst(threads))
 
74
        thread->wait();
 
75
    qDeleteAll(threads);
 
76
}
 
77
 
 
78
QStringList QWindowsFileSystemWatcherEngine::addPaths(const QStringList &paths,
 
79
                                                       QStringList *files,
 
80
                                                       QStringList *directories)
 
81
{
 
82
    DEBUG() << "Adding" << paths.count() << "to existing" << (files->count() + directories->count()) << "watchers";
 
83
    QStringList p = paths;
 
84
    QMutableListIterator<QString> it(p);
 
85
    while (it.hasNext()) {
 
86
        QString path = it.next();
 
87
        QString normalPath = path;
 
88
        if ((normalPath.endsWith(QLatin1Char('/')) && !normalPath.endsWith(QLatin1String(":/")))
 
89
            || (normalPath.endsWith(QLatin1Char('\\')) && !normalPath.endsWith(QLatin1String(":\\")))
 
90
#ifdef Q_OS_WINCE
 
91
            && normalPath.size() > 1)
 
92
#else
 
93
            )
 
94
#endif
 
95
        normalPath.chop(1);
 
96
        QFileInfo fileInfo(normalPath);
 
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
        DEBUG() << "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(QFileSystemWatcherPathKey(absolutePath));
 
138
            if (handle.handle != INVALID_HANDLE_VALUE && handle.flags == flags) {
 
139
                // found a thread now insert...
 
140
                DEBUG() << "Found a thread" << thread;
 
141
 
 
142
                QWindowsFileSystemWatcherEngineThread::PathInfoHash &h =
 
143
                    thread->pathInfoForHandle[handle.handle];
 
144
                const QFileSystemWatcherPathKey key(fileInfo.absoluteFilePath());
 
145
                if (!h.contains(key)) {
 
146
                    thread->pathInfoForHandle[handle.handle].insert(key, 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
            DEBUG() << "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;
 
173
            for (QWindowsFileSystemWatcherEngineThread *thread : qAsConst(threads)) {
 
174
                QMutexLocker locker(&(thread->mutex));
 
175
                if (thread->handles.count() < MAXIMUM_WAIT_OBJECTS) {
 
176
                    DEBUG() << "Added handle" << handle.handle << "for" << absolutePath << "to watch" << fileInfo.absoluteFilePath()
 
177
                            << "to existing thread " << thread;
 
178
                    thread->handles.append(handle.handle);
 
179
                    thread->handleForDir.insert(QFileSystemWatcherPathKey(absolutePath), handle);
 
180
 
 
181
                    thread->pathInfoForHandle[handle.handle].insert(QFileSystemWatcherPathKey(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) {
 
194
                QWindowsFileSystemWatcherEngineThread *thread = new QWindowsFileSystemWatcherEngineThread();
 
195
                DEBUG() << "  ###Creating new thread" << thread << '(' << (threads.count()+1) << "threads)";
 
196
                thread->handles.append(handle.handle);
 
197
                thread->handleForDir.insert(QFileSystemWatcherPathKey(absolutePath), handle);
 
198
 
 
199
                thread->pathInfoForHandle[handle.handle].insert(QFileSystemWatcherPathKey(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
 
 
220
QStringList QWindowsFileSystemWatcherEngine::removePaths(const QStringList &paths,
 
221
                                                          QStringList *files,
 
222
                                                          QStringList *directories)
 
223
{
 
224
    DEBUG() << "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);
 
233
        DEBUG() << "removing" << normalPath;
 
234
        QString absolutePath = fileInfo.absoluteFilePath();
 
235
        QList<QWindowsFileSystemWatcherEngineThread *>::iterator jt, end;
 
236
        end = threads.end();
 
237
        for(jt = threads.begin(); jt!= end; ++jt) {
 
238
            QWindowsFileSystemWatcherEngineThread *thread = *jt;
 
239
            if (*jt == 0)
 
240
                continue;
 
241
 
 
242
            QMutexLocker locker(&(thread->mutex));
 
243
 
 
244
            QWindowsFileSystemWatcherEngine::Handle handle = thread->handleForDir.value(QFileSystemWatcherPathKey(absolutePath));
 
245
            if (handle.handle == INVALID_HANDLE_VALUE) {
 
246
                // perhaps path is a file?
 
247
                absolutePath = fileInfo.absolutePath();
 
248
                handle = thread->handleForDir.value(QFileSystemWatcherPathKey(absolutePath));
 
249
            }
 
250
            if (handle.handle != INVALID_HANDLE_VALUE) {
 
251
                QWindowsFileSystemWatcherEngineThread::PathInfoHash &h =
 
252
                        thread->pathInfoForHandle[handle.handle];
 
253
                if (h.remove(QFileSystemWatcherPathKey(fileInfo.absoluteFilePath()))) {
 
254
                    // ###
 
255
                    files->removeAll(path);
 
256
                    directories->removeAll(path);
 
257
                    it.remove();
 
258
 
 
259
                    if (h.isEmpty()) {
 
260
                        DEBUG() << "Closing handle" << handle.handle;
 
261
                        FindCloseChangeNotification(handle.handle);    // This one might generate a notification
 
262
 
 
263
                        int indexOfHandle = thread->handles.indexOf(handle.handle);
 
264
                        Q_ASSERT(indexOfHandle != -1);
 
265
                        thread->handles.remove(indexOfHandle);
 
266
 
 
267
                        thread->handleForDir.remove(QFileSystemWatcherPathKey(absolutePath));
 
268
                        // h is now invalid
 
269
 
 
270
                        if (thread->handleForDir.isEmpty()) {
 
271
                            DEBUG() << "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
}
 
313
 
 
314
 
 
315
QWindowsFileSystemWatcherEngineThread::~QWindowsFileSystemWatcherEngineThread()
 
316
{
 
317
    CloseHandle(handles.at(0));
 
318
    handles[0] = INVALID_HANDLE_VALUE;
 
319
 
 
320
    for (HANDLE h : qAsConst(handles)) {
 
321
        if (h == INVALID_HANDLE_VALUE)
 
322
            continue;
 
323
        FindCloseChangeNotification(h);
 
324
    }
 
325
}
 
326
 
 
327
static inline QString msgFindNextFailed(const QWindowsFileSystemWatcherEngineThread::PathInfoHash &pathInfos)
 
328
{
 
329
    QString result;
 
330
    QTextStream str(&result);
 
331
    str << "QFileSystemWatcher: FindNextChangeNotification failed for";
 
332
    for (const QWindowsFileSystemWatcherEngine::PathInfo &pathInfo : pathInfos)
 
333
        str << " \"" << QDir::toNativeSeparators(pathInfo.absolutePath) << '"';
 
334
    str << ' ';
 
335
    return result;
 
336
}
 
337
 
 
338
void QWindowsFileSystemWatcherEngineThread::run()
 
339
{
 
340
    QMutexLocker locker(&mutex);
 
341
    forever {
 
342
        QVector<HANDLE> handlesCopy = handles;
 
343
        locker.unlock();
 
344
        DEBUG() << "QWindowsFileSystemWatcherThread" << this << "waiting on" << handlesCopy.count() << "handles";
 
345
        DWORD r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, INFINITE);
 
346
        locker.relock();
 
347
        do {
 
348
            if (r == WAIT_OBJECT_0) {
 
349
                int m = msg;
 
350
                msg = 0;
 
351
                if (m == 'q') {
 
352
                    DEBUG() << "thread" << this << "told to quit";
 
353
                    return;
 
354
                }
 
355
                if (m != '@')
 
356
                    DEBUG() << "QWindowsFileSystemWatcherEngine: unknown message sent to thread: " << char(m);
 
357
                break;
 
358
            } else if (r > WAIT_OBJECT_0 && r < WAIT_OBJECT_0 + uint(handlesCopy.count())) {
 
359
                int at = r - WAIT_OBJECT_0;
 
360
                Q_ASSERT(at < handlesCopy.count());
 
361
                HANDLE handle = handlesCopy.at(at);
 
362
 
 
363
                // When removing a path, FindCloseChangeNotification might actually fire a notification
 
364
                // for some reason, so we must check if the handle exist in the handles vector
 
365
                if (handles.contains(handle)) {
 
366
                    DEBUG() << "thread" << this << "Acknowledged handle:" << at << handle;
 
367
                    QWindowsFileSystemWatcherEngineThread::PathInfoHash &h = pathInfoForHandle[handle];
 
368
                    bool fakeRemove = false;
 
369
 
 
370
                    if (!FindNextChangeNotification(handle)) {
 
371
                        const DWORD error = GetLastError();
 
372
 
 
373
                        if (error == ERROR_ACCESS_DENIED) {
 
374
                            // for directories, our object's handle appears to be woken up when the target of a
 
375
                            // watch is deleted, before the watched thing is actually deleted...
 
376
                            // anyway.. we're given an error code of ERROR_ACCESS_DENIED in that case.
 
377
                            fakeRemove = true;
 
378
                        }
 
379
 
 
380
                        qErrnoWarning(error, "%s", qPrintable(msgFindNextFailed(h)));
 
381
                    }
 
382
                    QMutableHashIterator<QFileSystemWatcherPathKey, QWindowsFileSystemWatcherEngine::PathInfo> it(h);
 
383
                    while (it.hasNext()) {
 
384
                        QWindowsFileSystemWatcherEngineThread::PathInfoHash::iterator x = it.next();
 
385
                        QString absolutePath = x.value().absolutePath;
 
386
                        QFileInfo fileInfo(x.value().path);
 
387
                        DEBUG() << "checking" << x.key();
 
388
 
 
389
                        // i'm not completely sure the fileInfo.exist() check will ever work... see QTBUG-2331
 
390
                        // ..however, I'm not completely sure enough to remove it.
 
391
                        if (fakeRemove || !fileInfo.exists()) {
 
392
                            DEBUG() << x.key() << "removed!";
 
393
                            if (x.value().isDir)
 
394
                                emit directoryChanged(x.value().path, true);
 
395
                            else
 
396
                                emit fileChanged(x.value().path, true);
 
397
                            h.erase(x);
 
398
 
 
399
                            // close the notification handle if the directory has been removed
 
400
                            if (h.isEmpty()) {
 
401
                                DEBUG() << "Thread closing handle" << handle;
 
402
                                FindCloseChangeNotification(handle);    // This one might generate a notification
 
403
 
 
404
                                int indexOfHandle = handles.indexOf(handle);
 
405
                                Q_ASSERT(indexOfHandle != -1);
 
406
                                handles.remove(indexOfHandle);
 
407
 
 
408
                                handleForDir.remove(QFileSystemWatcherPathKey(absolutePath));
 
409
                                // h is now invalid
 
410
                            }
 
411
                        } else if (x.value().isDir) {
 
412
                            DEBUG() << x.key() << "directory changed!";
 
413
                            emit directoryChanged(x.value().path, false);
 
414
                            x.value() = fileInfo;
 
415
                        } else if (x.value() != fileInfo) {
 
416
                            DEBUG() << x.key() << "file changed!";
 
417
                            emit fileChanged(x.value().path, false);
 
418
                            x.value() = fileInfo;
 
419
                        }
 
420
                    }
 
421
                }
 
422
            } else {
 
423
                // qErrnoWarning("QFileSystemWatcher: error while waiting for change notification");
 
424
                break;  // avoid endless loop
 
425
            }
 
426
            handlesCopy = handles;
 
427
            r = WaitForMultipleObjects(handlesCopy.count(), handlesCopy.constData(), false, 0);
 
428
        } while (r != WAIT_TIMEOUT);
 
429
    }
 
430
}
 
431
 
 
432
 
 
433
void QWindowsFileSystemWatcherEngineThread::stop()
 
434
{
 
435
    msg = 'q';
 
436
    SetEvent(handles.at(0));
 
437
}
 
438
 
 
439
void QWindowsFileSystemWatcherEngineThread::wakeup()
 
440
{
 
441
    msg = '@';
 
442
    SetEvent(handles.at(0));
 
443
}
 
444
 
 
445
QT_END_NAMESPACE
 
446
#endif // QT_NO_FILESYSTEMWATCHER