2012-12-04 21:15:37 +04:00
|
|
|
/*
|
|
|
|
* Copyright (C) by Daniel Molkentin <danimo@owncloud.com>
|
|
|
|
*
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
2016-10-25 12:00:07 +03:00
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
* (at your option) any later version.
|
2012-12-04 21:15:37 +04:00
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful, but
|
|
|
|
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
|
|
|
|
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
|
* for more details.
|
|
|
|
*/
|
|
|
|
|
2012-12-05 21:23:35 +04:00
|
|
|
#include <QThread>
|
2014-11-06 02:36:04 +03:00
|
|
|
#include <QDir>
|
2012-12-05 21:23:35 +04:00
|
|
|
|
2015-07-03 17:24:23 +03:00
|
|
|
#include "filesystem.h"
|
2014-07-11 02:31:24 +04:00
|
|
|
#include "folderwatcher.h"
|
|
|
|
#include "folderwatcher_win.h"
|
2012-12-05 21:23:35 +04:00
|
|
|
|
2019-09-08 12:33:20 +03:00
|
|
|
#include "common/utility.h"
|
|
|
|
|
2012-12-05 21:23:35 +04:00
|
|
|
#include <stdlib.h>
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <tchar.h>
|
2012-12-04 21:15:37 +04:00
|
|
|
|
2014-11-10 00:34:07 +03:00
|
|
|
namespace OCC {
|
2012-12-04 21:15:37 +04:00
|
|
|
|
2014-11-06 02:36:04 +03:00
|
|
|
void WatcherThread::watchChanges(size_t fileNotifyBufferSize,
|
2017-05-17 11:55:42 +03:00
|
|
|
bool *increaseBufferSize)
|
2012-12-05 21:23:35 +04:00
|
|
|
{
|
2014-11-06 02:36:04 +03:00
|
|
|
*increaseBufferSize = false;
|
2015-07-03 17:24:23 +03:00
|
|
|
QString longPath = FileSystem::longWinPath(_path);
|
2014-11-06 02:36:04 +03:00
|
|
|
|
2016-03-31 15:04:53 +03:00
|
|
|
_directory = CreateFileW(
|
2017-05-17 11:55:42 +03:00
|
|
|
(wchar_t *)longPath.utf16(),
|
2014-11-06 02:36:04 +03:00
|
|
|
FILE_LIST_DIRECTORY,
|
|
|
|
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
|
2020-06-05 01:51:32 +03:00
|
|
|
nullptr,
|
2014-11-06 02:36:04 +03:00
|
|
|
OPEN_EXISTING,
|
2016-03-31 15:04:53 +03:00
|
|
|
FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED,
|
2020-06-05 01:51:32 +03:00
|
|
|
nullptr);
|
2012-12-04 21:15:37 +04:00
|
|
|
|
2017-05-17 11:55:42 +03:00
|
|
|
if (_directory == INVALID_HANDLE_VALUE) {
|
2014-11-06 02:36:04 +03:00
|
|
|
DWORD errorCode = GetLastError();
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcFolderWatcher) << "Failed to create handle for" << _path << ", error:" << errorCode;
|
2016-03-31 15:04:53 +03:00
|
|
|
_directory = 0;
|
2012-12-05 21:23:35 +04:00
|
|
|
return;
|
|
|
|
}
|
2012-12-04 21:15:37 +04:00
|
|
|
|
2016-03-31 15:04:53 +03:00
|
|
|
OVERLAPPED overlapped;
|
|
|
|
overlapped.hEvent = _resultEvent;
|
|
|
|
|
2014-11-06 02:36:04 +03:00
|
|
|
// QVarLengthArray ensures the stack-buffer is aligned like double and qint64.
|
2017-05-17 11:55:42 +03:00
|
|
|
QVarLengthArray<char, 4096 * 10> fileNotifyBuffer;
|
2019-09-08 12:33:20 +03:00
|
|
|
fileNotifyBuffer.resize(OCC::Utility::convertSizeToInt(fileNotifyBufferSize));
|
2014-11-06 02:36:04 +03:00
|
|
|
|
|
|
|
const size_t fileNameBufferSize = 4096;
|
|
|
|
TCHAR fileNameBuffer[fileNameBufferSize];
|
|
|
|
|
2016-03-31 15:04:53 +03:00
|
|
|
|
|
|
|
while (!_done) {
|
|
|
|
ResetEvent(_resultEvent);
|
|
|
|
|
2014-11-06 02:36:04 +03:00
|
|
|
FILE_NOTIFY_INFORMATION *pFileNotifyBuffer =
|
2017-05-17 11:55:42 +03:00
|
|
|
(FILE_NOTIFY_INFORMATION *)fileNotifyBuffer.data();
|
2014-11-06 02:36:04 +03:00
|
|
|
DWORD dwBytesReturned = 0;
|
|
|
|
SecureZeroMemory(pFileNotifyBuffer, fileNotifyBufferSize);
|
2017-05-17 11:55:42 +03:00
|
|
|
if (!ReadDirectoryChangesW(_directory, (LPVOID)pFileNotifyBuffer,
|
2019-09-08 12:33:20 +03:00
|
|
|
OCC::Utility::convertSizeToDWORD(fileNotifyBufferSize), true,
|
2019-01-23 17:12:02 +03:00
|
|
|
FILE_NOTIFY_CHANGE_FILE_NAME
|
|
|
|
| FILE_NOTIFY_CHANGE_DIR_NAME
|
|
|
|
| FILE_NOTIFY_CHANGE_LAST_WRITE
|
|
|
|
| FILE_NOTIFY_CHANGE_ATTRIBUTES, // attributes are for vfs pin state changes
|
2017-05-17 11:55:42 +03:00
|
|
|
&dwBytesReturned,
|
|
|
|
&overlapped,
|
2020-06-05 01:51:32 +03:00
|
|
|
nullptr)) {
|
2016-03-31 15:04:53 +03:00
|
|
|
DWORD errorCode = GetLastError();
|
|
|
|
if (errorCode == ERROR_NOTIFY_ENUM_DIR) {
|
2017-05-09 15:24:11 +03:00
|
|
|
qCDebug(lcFolderWatcher) << "The buffer for changes overflowed! Triggering a generic change and resizing";
|
2016-03-31 15:04:53 +03:00
|
|
|
emit changed(_path);
|
|
|
|
*increaseBufferSize = true;
|
|
|
|
} else {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcFolderWatcher) << "ReadDirectoryChangesW error" << errorCode;
|
2012-12-06 19:26:27 +04:00
|
|
|
}
|
2016-03-31 15:04:53 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2019-07-11 14:31:54 +03:00
|
|
|
emit ready();
|
|
|
|
|
2017-05-17 11:55:42 +03:00
|
|
|
HANDLE handles[] = { _resultEvent, _stopEvent };
|
2016-03-31 15:04:53 +03:00
|
|
|
DWORD result = WaitForMultipleObjects(
|
2017-05-17 11:55:42 +03:00
|
|
|
2, handles,
|
|
|
|
false, // awake once one of them arrives
|
|
|
|
INFINITE);
|
2016-03-31 15:04:53 +03:00
|
|
|
if (result == 1) {
|
2017-05-09 15:24:11 +03:00
|
|
|
qCDebug(lcFolderWatcher) << "Received stop event, aborting folder watcher thread";
|
2016-03-31 15:04:53 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
if (result != 0) {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcFolderWatcher) << "WaitForMultipleObjects failed" << result << GetLastError();
|
2016-03-31 15:04:53 +03:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
bool ok = GetOverlappedResult(_directory, &overlapped, &dwBytesReturned, false);
|
2017-05-17 11:55:42 +03:00
|
|
|
if (!ok) {
|
2014-11-06 02:36:04 +03:00
|
|
|
DWORD errorCode = GetLastError();
|
2016-03-31 15:04:53 +03:00
|
|
|
if (errorCode == ERROR_NOTIFY_ENUM_DIR) {
|
2017-05-09 15:24:11 +03:00
|
|
|
qCDebug(lcFolderWatcher) << "The buffer for changes overflowed! Triggering a generic change and resizing";
|
2017-10-09 13:06:57 +03:00
|
|
|
emit lostChanges();
|
2014-11-06 02:36:04 +03:00
|
|
|
emit changed(_path);
|
|
|
|
*increaseBufferSize = true;
|
2016-03-31 15:04:53 +03:00
|
|
|
} else {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcFolderWatcher) << "GetOverlappedResult error" << errorCode;
|
2016-03-31 15:04:53 +03:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
FILE_NOTIFY_INFORMATION *curEntry = pFileNotifyBuffer;
|
|
|
|
forever {
|
|
|
|
size_t len = curEntry->FileNameLength / 2;
|
2019-09-08 12:33:20 +03:00
|
|
|
QString file = _path + "\\" + QString::fromWCharArray(curEntry->FileName, OCC::Utility::convertSizeToInt(len));
|
2016-03-31 15:04:53 +03:00
|
|
|
|
|
|
|
// Unless the file was removed or renamed, get its full long name
|
|
|
|
// TODO: We could still try expanding the path in the tricky cases...
|
|
|
|
QString longfile = file;
|
|
|
|
if (curEntry->Action != FILE_ACTION_REMOVED
|
2017-05-17 11:55:42 +03:00
|
|
|
&& curEntry->Action != FILE_ACTION_RENAMED_OLD_NAME) {
|
2016-03-31 15:04:53 +03:00
|
|
|
size_t longNameSize = GetLongPathNameW(reinterpret_cast<LPCWSTR>(file.utf16()), fileNameBuffer, fileNameBufferSize);
|
|
|
|
if (longNameSize > 0) {
|
2019-09-08 12:33:20 +03:00
|
|
|
longfile = QString::fromUtf16(reinterpret_cast<const ushort *>(fileNameBuffer), OCC::Utility::convertSizeToInt(longNameSize));
|
2016-03-31 15:04:53 +03:00
|
|
|
} else {
|
2017-03-30 14:46:20 +03:00
|
|
|
qCWarning(lcFolderWatcher) << "Error converting file name to full length, keeping original name.";
|
2016-03-31 15:04:53 +03:00
|
|
|
}
|
|
|
|
}
|
|
|
|
longfile = QDir::cleanPath(longfile);
|
|
|
|
|
|
|
|
// Skip modifications of folders: One of these is triggered for changes
|
|
|
|
// and new files in a folder, probably because of the folder's mtime
|
|
|
|
// changing. We don't need them.
|
|
|
|
bool skip = curEntry->Action == FILE_ACTION_MODIFIED
|
2017-05-17 11:55:42 +03:00
|
|
|
&& QFileInfo(longfile).isDir();
|
2016-03-31 15:04:53 +03:00
|
|
|
|
|
|
|
if (!skip) {
|
|
|
|
emit changed(longfile);
|
2020-12-03 14:24:25 +03:00
|
|
|
} else {
|
|
|
|
qCDebug(lcFolderWatcher) << "Skipping syncing of" << longfile;
|
2016-03-31 15:04:53 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
if (curEntry->NextEntryOffset == 0) {
|
2014-11-06 02:36:04 +03:00
|
|
|
break;
|
|
|
|
}
|
2017-05-17 11:55:42 +03:00
|
|
|
curEntry = (FILE_NOTIFY_INFORMATION *)((char *)curEntry + curEntry->NextEntryOffset);
|
2014-11-06 02:36:04 +03:00
|
|
|
}
|
|
|
|
}
|
2016-03-31 15:04:53 +03:00
|
|
|
|
|
|
|
CancelIo(_directory);
|
|
|
|
closeHandle();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatcherThread::closeHandle()
|
|
|
|
{
|
|
|
|
if (_directory) {
|
|
|
|
CloseHandle(_directory);
|
2020-06-05 01:51:32 +03:00
|
|
|
_directory = nullptr;
|
2016-03-31 15:04:53 +03:00
|
|
|
}
|
2014-11-06 02:36:04 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
void WatcherThread::run()
|
|
|
|
{
|
2020-06-05 01:51:32 +03:00
|
|
|
_resultEvent = CreateEvent(nullptr, true, false, nullptr);
|
|
|
|
_stopEvent = CreateEvent(nullptr, true, false, nullptr);
|
2016-03-31 15:04:53 +03:00
|
|
|
|
2014-11-06 02:36:04 +03:00
|
|
|
// If this buffer fills up before we've extracted its data we will lose
|
|
|
|
// change information. Therefore start big.
|
2017-05-17 11:55:42 +03:00
|
|
|
size_t bufferSize = 4096 * 10;
|
|
|
|
size_t maxBuffer = 64 * 1024;
|
2014-11-06 02:36:04 +03:00
|
|
|
|
2016-03-31 15:04:53 +03:00
|
|
|
while (!_done) {
|
2014-11-06 02:36:04 +03:00
|
|
|
bool increaseBufferSize = false;
|
|
|
|
watchChanges(bufferSize, &increaseBufferSize);
|
|
|
|
|
|
|
|
if (increaseBufferSize) {
|
2017-05-17 11:55:42 +03:00
|
|
|
bufferSize = qMin(bufferSize * 2, maxBuffer);
|
2016-03-31 15:04:53 +03:00
|
|
|
} else if (!_done) {
|
2014-11-06 02:36:04 +03:00
|
|
|
// Other errors shouldn't actually happen,
|
|
|
|
// so sleep a bit to avoid running into the same error case in a
|
|
|
|
// tight loop.
|
|
|
|
sleep(2);
|
2012-12-05 21:23:35 +04:00
|
|
|
}
|
|
|
|
}
|
2012-12-04 21:15:37 +04:00
|
|
|
}
|
2012-12-06 19:26:27 +04:00
|
|
|
|
|
|
|
WatcherThread::~WatcherThread()
|
|
|
|
{
|
2016-03-31 15:04:53 +03:00
|
|
|
closeHandle();
|
|
|
|
}
|
|
|
|
|
|
|
|
void WatcherThread::stop()
|
|
|
|
{
|
|
|
|
_done = 1;
|
|
|
|
SetEvent(_stopEvent);
|
2012-12-06 19:26:27 +04:00
|
|
|
}
|
2012-12-05 21:23:35 +04:00
|
|
|
|
2017-05-17 11:55:42 +03:00
|
|
|
FolderWatcherPrivate::FolderWatcherPrivate(FolderWatcher *p, const QString &path)
|
2012-12-05 21:23:35 +04:00
|
|
|
: _parent(p)
|
|
|
|
{
|
2014-01-13 18:23:52 +04:00
|
|
|
_thread = new WatcherThread(path);
|
2017-05-17 11:55:42 +03:00
|
|
|
connect(_thread, SIGNAL(changed(const QString &)),
|
|
|
|
_parent, SLOT(changeDetected(const QString &)));
|
2017-10-09 13:06:57 +03:00
|
|
|
connect(_thread, SIGNAL(lostChanges()),
|
|
|
|
_parent, SIGNAL(lostChanges()));
|
2019-07-11 14:31:54 +03:00
|
|
|
connect(_thread, &WatcherThread::ready,
|
|
|
|
this, [this]() { _ready = 1; });
|
2012-12-06 19:26:27 +04:00
|
|
|
_thread->start();
|
2012-12-05 21:23:35 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
FolderWatcherPrivate::~FolderWatcherPrivate()
|
|
|
|
{
|
2016-03-31 15:04:53 +03:00
|
|
|
_thread->stop();
|
2012-12-06 19:26:27 +04:00
|
|
|
_thread->wait();
|
2012-12-05 21:23:35 +04:00
|
|
|
delete _thread;
|
|
|
|
}
|
|
|
|
|
2014-11-10 00:34:07 +03:00
|
|
|
} // namespace OCC
|