| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652 |
- #include "CmWin32FolderMonitor.h"
- #include "CmFileSystem.h"
- #include "CmException.h"
- #include <windows.h>
- namespace CamelotFramework
- {
- enum class MonitorState
- {
- Inactive,
- Starting,
- Monitoring,
- Shutdown,
- Shutdown2
- };
- class WorkerFunc
- {
- public:
- WorkerFunc(Win32FolderMonitor* owner);
- void operator()();
- private:
- Win32FolderMonitor* mOwner;
- };
- struct Win32FolderMonitor::FolderWatchInfo
- {
- FolderWatchInfo(const WString& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags);
- ~FolderWatchInfo();
- void startMonitor(HANDLE compPortHandle);
- void stopMonitor(HANDLE compPortHandle);
- static const UINT32 READ_BUFFER_SIZE = 65536;
- WString mFolderToMonitor;
- HANDLE mDirHandle;
- OVERLAPPED mOverlapped;
- MonitorState mState;
- UINT8 mBuffer[READ_BUFFER_SIZE];
- DWORD mBufferSize;
- bool mMonitorSubdirectories;
- DWORD mMonitorFlags;
- DWORD mReadError;
- WString mCachedOldFileName; // Used during rename notifications as they are handled in two steps
- CM_MUTEX(mStatusMutex)
- CM_THREAD_SYNCHRONISER(mStartStopEvent)
- };
- Win32FolderMonitor::FolderWatchInfo::FolderWatchInfo(const WString& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags)
- :mFolderToMonitor(folderToMonitor), mDirHandle(dirHandle), mState(MonitorState::Inactive), mBufferSize(0),
- mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags), mReadError(0)
- {
- StringUtil::trim(mFolderToMonitor, L"\\", false, true);
- StringUtil::toLowerCase(mFolderToMonitor);
- memset(&mOverlapped, 0, sizeof(mOverlapped));
- }
- Win32FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
- {
- assert(mState == MonitorState::Inactive);
- stopMonitor(0);
- }
- void Win32FolderMonitor::FolderWatchInfo::startMonitor(HANDLE compPortHandle)
- {
- if(mState != MonitorState::Inactive)
- return; // Already monitoring
- {
- CM_LOCK_MUTEX_NAMED(mStatusMutex, lock);
- mState = MonitorState::Starting;
- PostQueuedCompletionStatus(compPortHandle, sizeof(this), (DWORD)this, &mOverlapped);
- while(mState != MonitorState::Monitoring)
- CM_THREAD_WAIT(mStartStopEvent, mStatusMutex, lock);
- }
- if(mReadError != ERROR_SUCCESS)
- {
- {
- CM_LOCK_MUTEX(mStatusMutex);
- mState = MonitorState::Inactive;
- }
- CM_EXCEPT(InternalErrorException, "Failed to start folder monitor on folder \"" +
- toString(mFolderToMonitor) + "\" because ReadDirectoryChangesW failed.");
- }
- }
- void Win32FolderMonitor::FolderWatchInfo::stopMonitor(HANDLE compPortHandle)
- {
- if(mState != MonitorState::Inactive)
- {
- CM_LOCK_MUTEX_NAMED(mStatusMutex, lock);
- mState = MonitorState::Shutdown;
- PostQueuedCompletionStatus(compPortHandle, sizeof(this), (DWORD)this, &mOverlapped);
- while(mState != MonitorState::Inactive)
- CM_THREAD_WAIT(mStartStopEvent, mStatusMutex, lock);
- }
- if(mDirHandle != INVALID_HANDLE_VALUE)
- {
- CloseHandle(mDirHandle);
- mDirHandle = INVALID_HANDLE_VALUE;
- }
- }
- class Win32FolderMonitor::FileNotifyInfo
- {
- public:
- FileNotifyInfo(UINT8* notifyBuffer, DWORD bufferSize)
- :mBuffer(notifyBuffer), mBufferSize(bufferSize)
- {
- mCurrentRecord = (PFILE_NOTIFY_INFORMATION)mBuffer;
- }
- bool getNext();
-
- DWORD getAction() const;
- WString getFileName() const;
- WString getFileNameWithPath(const WString& rootPath) const;
-
- protected:
- UINT8* mBuffer;
- DWORD mBufferSize;
- PFILE_NOTIFY_INFORMATION mCurrentRecord;
- };
- bool Win32FolderMonitor::FileNotifyInfo::getNext()
- {
- if(mCurrentRecord && mCurrentRecord->NextEntryOffset != 0)
- {
- PFILE_NOTIFY_INFORMATION oldRecord = mCurrentRecord;
- mCurrentRecord = (PFILE_NOTIFY_INFORMATION) ((UINT8*)mCurrentRecord + mCurrentRecord->NextEntryOffset);
- if((DWORD)((UINT8*)mCurrentRecord - mBuffer) > mBufferSize)
- {
- // Gone out of range, something bad happened
- assert(false);
- mCurrentRecord = oldRecord;
- }
-
- return (mCurrentRecord != oldRecord);
- }
- return false;
- }
- DWORD Win32FolderMonitor::FileNotifyInfo::getAction() const
- {
- assert(mCurrentRecord != nullptr);
- if(mCurrentRecord)
- return mCurrentRecord->Action;
- return 0;
- }
- WString Win32FolderMonitor::FileNotifyInfo::getFileName() const
- {
- if(mCurrentRecord)
- {
- wchar_t fileNameBuffer[32768 + 1] = {0};
- memcpy(fileNameBuffer, mCurrentRecord->FileName,
- std::min(DWORD(32768 * sizeof(wchar_t)), mCurrentRecord->FileNameLength));
-
- return WString(fileNameBuffer);
- }
- return WString();
- }
- WString Win32FolderMonitor::FileNotifyInfo::getFileNameWithPath(const WString& rootPath) const
- {
- WStringStream wss;
- wss<<rootPath;
- wss<<L"\\";
- wss<<getFileName();
- return wss.str();
- }
- enum class FileActionType
- {
- Added,
- Removed,
- Modified,
- Renamed
- };
- struct FileAction
- {
- static FileAction* createAdded(const WString& fileName)
- {
- UINT8* bytes = (UINT8*)cm_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
- FileAction* action = (FileAction*)bytes;
- bytes += sizeof(FileAction);
- action->oldName = nullptr;
- action->newName = (WString::value_type*)bytes;
- action->type = FileActionType::Added;
- memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
- action->newName[fileName.size()] = L'\0';
- return action;
- }
- static FileAction* createRemoved(const WString& fileName)
- {
- UINT8* bytes = (UINT8*)cm_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
- FileAction* action = (FileAction*)bytes;
- bytes += sizeof(FileAction);
- action->oldName = nullptr;
- action->newName = (WString::value_type*)bytes;
- action->type = FileActionType::Removed;
- memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
- action->newName[fileName.size()] = L'\0';
- return action;
- }
- static FileAction* createModified(const WString& fileName)
- {
- UINT8* bytes = (UINT8*)cm_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
- FileAction* action = (FileAction*)bytes;
- bytes += sizeof(FileAction);
- action->oldName = nullptr;
- action->newName = (WString::value_type*)bytes;
- action->type = FileActionType::Modified;
- memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
- action->newName[fileName.size()] = L'\0';
- return action;
- }
- static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
- {
- UINT8* bytes = (UINT8*)cm_alloc((UINT32)(sizeof(FileAction) +
- (oldFilename.size() + newfileName.size() + 2) * sizeof(WString::value_type)));
- FileAction* action = (FileAction*)bytes;
- bytes += sizeof(FileAction);
- action->oldName = (WString::value_type*)bytes;
- bytes += (oldFilename.size() + 1) * sizeof(WString::value_type);
- action->newName = (WString::value_type*)bytes;
- action->type = FileActionType::Modified;
- memcpy(action->oldName, oldFilename.data(), oldFilename.size() * sizeof(WString::value_type));
- action->oldName[oldFilename.size()] = L'\0';
- memcpy(action->newName, newfileName.data(), newfileName.size() * sizeof(WString::value_type));
- action->newName[newfileName.size()] = L'\0';
- return action;
- }
- static void destroy(FileAction* action)
- {
- cm_free(action);
- }
- WString::value_type* oldName;
- WString::value_type* newName;
- FileActionType type;
- };
- struct Win32FolderMonitor::Pimpl
- {
- Vector<FolderWatchInfo*>::type mFoldersToWatch;
- HANDLE mCompPortHandle;
- Queue<FileAction*>::type mFileActions;
- Queue<FileAction*>::type mActiveFileActions;
- CM_MUTEX(mFileActionsMutex);
- CM_THREAD_TYPE* mWorkerThread;
- };
- Win32FolderMonitor::Win32FolderMonitor()
- {
- mPimpl = cm_new<Pimpl>();
- mPimpl->mWorkerThread = nullptr;
- mPimpl->mCompPortHandle = nullptr;
- }
- Win32FolderMonitor::~Win32FolderMonitor()
- {
- stopMonitorAll();
- // No need for mutex since we know worker thread is shut down by now
- while(!mPimpl->mFileActions.empty())
- {
- FileAction* action = mPimpl->mFileActions.front();
- mPimpl->mFileActions.pop();
- FileAction::destroy(action);
- }
- cm_delete(mPimpl);
- }
- void Win32FolderMonitor::startMonitor(const WString& folderPath, bool subdirectories, FolderChange changeFilter)
- {
- if(!FileSystem::isDirectory(folderPath))
- {
- CM_EXCEPT(InvalidParametersException, "Provided path \"" + toString(folderPath) + "\" is not a directory");
- }
- WString extendedFolderPath = L"\\\\?\\" + folderPath;
- HANDLE dirHandle = CreateFileW(folderPath.c_str(), FILE_LIST_DIRECTORY,
- FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
- FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
- if(dirHandle == INVALID_HANDLE_VALUE)
- {
- CM_EXCEPT(InternalErrorException, "Failed to open folder \"" + toString(folderPath) + "\" for monitoring. Error code: " + toString(GetLastError()));
- }
- DWORD filterFlags = 0;
- if((((UINT32)changeFilter) & (UINT32)CamelotFramework::FolderChange::FileName) != 0)
- filterFlags |= FILE_NOTIFY_CHANGE_FILE_NAME;
- if((((UINT32)changeFilter) & (UINT32)CamelotFramework::FolderChange::DirName) != 0)
- filterFlags |= FILE_NOTIFY_CHANGE_DIR_NAME;
- if((((UINT32)changeFilter) & (UINT32)CamelotFramework::FolderChange::Attributes) != 0)
- filterFlags |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
- if((((UINT32)changeFilter) & (UINT32)CamelotFramework::FolderChange::Size) != 0)
- filterFlags |= FILE_NOTIFY_CHANGE_SIZE;
- if((((UINT32)changeFilter) & (UINT32)CamelotFramework::FolderChange::LastWrite) != 0)
- filterFlags |= FILE_NOTIFY_CHANGE_LAST_WRITE;
- if((((UINT32)changeFilter) & (UINT32)CamelotFramework::FolderChange::LastAccess) != 0)
- filterFlags |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
- if((((UINT32)changeFilter) & (UINT32)CamelotFramework::FolderChange::Creation) != 0)
- filterFlags |= FILE_NOTIFY_CHANGE_CREATION;
- if((((UINT32)changeFilter) & (UINT32)CamelotFramework::FolderChange::Security) != 0)
- filterFlags |= FILE_NOTIFY_CHANGE_SECURITY;
- mPimpl->mFoldersToWatch.push_back(cm_new<FolderWatchInfo>(folderPath, dirHandle, subdirectories, filterFlags));
- FolderWatchInfo* watchInfo = mPimpl->mFoldersToWatch.back();
- mPimpl->mCompPortHandle = CreateIoCompletionPort(dirHandle, mPimpl->mCompPortHandle, (DWORD)watchInfo, 0);
- if(mPimpl->mCompPortHandle == nullptr)
- {
- mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
- cm_delete(watchInfo);
- CM_EXCEPT(InternalErrorException, "Failed to open completition port for folder monitoring. Error code: " + toString(GetLastError()));
- }
- if(mPimpl->mWorkerThread == nullptr)
- {
- CM_THREAD_CREATE(t, (boost::bind(&Win32FolderMonitor::workerThreadMain, this)));
- mPimpl->mWorkerThread = t;
- if(mPimpl->mWorkerThread == nullptr)
- {
- mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
- cm_delete(watchInfo);
- CM_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
- }
- }
- if(mPimpl->mWorkerThread != nullptr)
- {
- try
- {
- watchInfo->startMonitor(mPimpl->mCompPortHandle);
- }
- catch (Exception* e)
- {
- mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
- cm_delete(watchInfo);
- throw(e);
- }
- }
- else
- {
- mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
- cm_delete(watchInfo);
- CM_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
- }
- }
- void Win32FolderMonitor::stopMonitor(const WString& folderPath)
- {
- WString folderPathForComparison = folderPath;
- StringUtil::trim(folderPathForComparison, L"\\", false, true);
- StringUtil::toLowerCase(folderPathForComparison);
- auto findIter = std::find_if(mPimpl->mFoldersToWatch.begin(), mPimpl->mFoldersToWatch.end(),
- [&] (const FolderWatchInfo* x) { return x->mFolderToMonitor == folderPathForComparison; });
- if(findIter != mPimpl->mFoldersToWatch.end())
- {
- FolderWatchInfo* watchInfo = *findIter;
- watchInfo->stopMonitor(mPimpl->mCompPortHandle);
- cm_delete(watchInfo);
- mPimpl->mFoldersToWatch.erase(findIter);
- }
- if(mPimpl->mFoldersToWatch.size() == 0)
- stopMonitorAll();
- }
- void Win32FolderMonitor::stopMonitorAll()
- {
- for(auto& watchInfo : mPimpl->mFoldersToWatch)
- {
- watchInfo->stopMonitor(mPimpl->mCompPortHandle);
- cm_delete(watchInfo);
- }
- mPimpl->mFoldersToWatch.clear();
- if(mPimpl->mWorkerThread != nullptr)
- {
- PostQueuedCompletionStatus(mPimpl->mCompPortHandle, 0, 0, nullptr);
- mPimpl->mWorkerThread->join();
- CM_THREAD_DESTROY(mPimpl->mWorkerThread);
- mPimpl->mWorkerThread = nullptr;
- }
- if(mPimpl->mCompPortHandle != nullptr)
- {
- CloseHandle(mPimpl->mCompPortHandle);
- mPimpl->mCompPortHandle = nullptr;
- }
- }
- void Win32FolderMonitor::workerThreadMain()
- {
- FolderWatchInfo* watchInfo = nullptr;
- do
- {
- DWORD numBytes;
- LPOVERLAPPED overlapped;
- if(!GetQueuedCompletionStatus(mPimpl->mCompPortHandle, &numBytes, (PULONG_PTR) &watchInfo, &overlapped, INFINITE))
- {
- assert(false);
- // TODO: Folder handle was lost most likely. Not sure how to deal with that. Shutdown watch on this folder and cleanup?
- }
- if(watchInfo != nullptr)
- {
- MonitorState state;
- {
- CM_LOCK_MUTEX(watchInfo->mStatusMutex);
- state = watchInfo->mState;
- }
- switch(state)
- {
- case MonitorState::Starting:
- if(!ReadDirectoryChangesW(watchInfo->mDirHandle, watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE,
- watchInfo->mMonitorSubdirectories, watchInfo->mMonitorFlags, &watchInfo->mBufferSize, &watchInfo->mOverlapped, nullptr))
- {
- assert(false); // TODO - Possibly the buffer was too small?
- watchInfo->mReadError = GetLastError();
- }
- else
- {
- watchInfo->mReadError = ERROR_SUCCESS;
- {
- CM_LOCK_MUTEX(watchInfo->mStatusMutex);
- watchInfo->mState = MonitorState::Monitoring;
- }
- }
- CM_THREAD_NOTIFY_ONE(watchInfo->mStartStopEvent);
- break;
- case MonitorState::Monitoring:
- {
- FileNotifyInfo info(watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE);
- handleNotifications(info, *watchInfo);
- if(!ReadDirectoryChangesW(watchInfo->mDirHandle, watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE,
- watchInfo->mMonitorSubdirectories, watchInfo->mMonitorFlags, &watchInfo->mBufferSize, &watchInfo->mOverlapped, nullptr))
- {
- assert(false); // TODO: Failed during normal operation, possibly the buffer was too small. Shutdown watch on this folder and cleanup?
- watchInfo->mReadError = GetLastError();
- }
- else
- {
- watchInfo->mReadError = ERROR_SUCCESS;
- }
- }
- break;
- case MonitorState::Shutdown:
- if(watchInfo->mDirHandle != INVALID_HANDLE_VALUE)
- {
- CloseHandle(watchInfo->mDirHandle);
- watchInfo->mDirHandle = INVALID_HANDLE_VALUE;
- {
- CM_LOCK_MUTEX(watchInfo->mStatusMutex);
- watchInfo->mState = MonitorState::Shutdown2;
- }
- }
- else
- {
- watchInfo->mState = MonitorState::Inactive;
- CM_THREAD_NOTIFY_ONE(watchInfo->mStartStopEvent);
- }
- break;
- case MonitorState::Shutdown2:
- if(watchInfo->mDirHandle != INVALID_HANDLE_VALUE)
- {
- // Handle is still open? Try again.
- CloseHandle(watchInfo->mDirHandle);
- watchInfo->mDirHandle = INVALID_HANDLE_VALUE;
- }
- else
- {
- watchInfo->mState = MonitorState::Inactive;
- CM_THREAD_NOTIFY_ONE(watchInfo->mStartStopEvent);
- }
- break;
- }
- }
- } while (watchInfo != nullptr);
- }
- void Win32FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
- {
- Vector<FileAction*>::type mActions;
- do
- {
- switch(notifyInfo.getAction())
- {
- case FILE_ACTION_ADDED:
- {
- WString fileName = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
- mActions.push_back(FileAction::createAdded(fileName));
- }
- break;
- case FILE_ACTION_REMOVED:
- {
- WString fileName = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
- mActions.push_back(FileAction::createRemoved(fileName));
- }
- break;
- case FILE_ACTION_MODIFIED:
- {
- WString fileName = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
- mActions.push_back(FileAction::createModified(fileName));
- }
- break;
- case FILE_ACTION_RENAMED_OLD_NAME:
- {
- WString fileName = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
- watchInfo.mCachedOldFileName = fileName;
- }
- break;
- case FILE_ACTION_RENAMED_NEW_NAME:
- {
- WString fileName = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
- mActions.push_back(FileAction::createRenamed(watchInfo.mCachedOldFileName, fileName));
- }
- break;
- }
-
- } while(notifyInfo.getNext());
- {
- CM_LOCK_MUTEX(mPimpl->mFileActionsMutex);
- for(auto& action : mActions)
- mPimpl->mFileActions.push(action);
- }
- }
- void Win32FolderMonitor::update()
- {
- {
- CM_LOCK_MUTEX(mPimpl->mFileActionsMutex);
- mPimpl->mActiveFileActions.swap(mPimpl->mFileActions);
- }
- while(!mPimpl->mActiveFileActions.empty())
- {
- FileAction* action = mPimpl->mActiveFileActions.front();
- mPimpl->mActiveFileActions.pop();
- switch(action->type)
- {
- case FileActionType::Added:
- if(!onAdded.empty())
- onAdded(WString(action->newName));
- break;
- case FileActionType::Removed:
- if(!onRemoved.empty())
- onRemoved(WString(action->newName));
- break;
- case FileActionType::Modified:
- if(!onModified.empty())
- onModified(WString(action->newName));
- break;
- case FileActionType::Renamed:
- if(!onRenamed.empty())
- onRenamed(WString(action->oldName), WString(action->newName));
- break;
- }
- FileAction::destroy(action);
- }
- }
- }
|