Răsfoiți Sursa

Win32FolderMonitor (untested)

Marko Pintera 12 ani în urmă
părinte
comite
0ffa60480f

+ 2 - 0
CamelotCore/CamelotCore.vcxproj

@@ -411,6 +411,7 @@
     <ClInclude Include="Include\CmShader.h" />
     <ClInclude Include="Include\CmShader.h" />
     <ClInclude Include="Include\CmBlendState.h" />
     <ClInclude Include="Include\CmBlendState.h" />
     <ClInclude Include="Include\CmWin32Defs.h" />
     <ClInclude Include="Include\CmWin32Defs.h" />
+    <ClInclude Include="Include\CmWin32FolderMonitor.h" />
     <ClInclude Include="Include\stdafx.h" />
     <ClInclude Include="Include\stdafx.h" />
     <ClInclude Include="Include\targetver.h" />
     <ClInclude Include="Include\targetver.h" />
     <ClInclude Include="Include\CmVertexDeclarationRTTI.h" />
     <ClInclude Include="Include\CmVertexDeclarationRTTI.h" />
@@ -519,6 +520,7 @@
     <ClCompile Include="Source\CmViewport.cpp" />
     <ClCompile Include="Source\CmViewport.cpp" />
     <ClCompile Include="Source\CmSceneObject.cpp" />
     <ClCompile Include="Source\CmSceneObject.cpp" />
     <ClCompile Include="Source\CmComponent.cpp" />
     <ClCompile Include="Source\CmComponent.cpp" />
+    <ClCompile Include="Source\CmWin32FolderMonitor.cpp" />
     <ClCompile Include="Source\stdafx.cpp" />
     <ClCompile Include="Source\stdafx.cpp" />
     <ClCompile Include="Source\Win32\CmPlatformImpl.cpp" />
     <ClCompile Include="Source\Win32\CmPlatformImpl.cpp" />
   </ItemGroup>
   </ItemGroup>

+ 9 - 0
CamelotCore/CamelotCore.vcxproj.filters

@@ -94,6 +94,9 @@
     <Filter Include="Header Files\Platform">
     <Filter Include="Header Files\Platform">
       <UniqueIdentifier>{d53f502a-b966-4162-a828-af2654f0408f}</UniqueIdentifier>
       <UniqueIdentifier>{d53f502a-b966-4162-a828-af2654f0408f}</UniqueIdentifier>
     </Filter>
     </Filter>
+    <Filter Include="Source Files\Platform">
+      <UniqueIdentifier>{88dfbdf1-6999-424c-ac32-1ffe65b6c9f6}</UniqueIdentifier>
+    </Filter>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClInclude Include="Include\CmApplication.h">
     <ClInclude Include="Include\CmApplication.h">
@@ -534,6 +537,9 @@
     <ClInclude Include="Include\CmResourceManifestRTTI.h">
     <ClInclude Include="Include\CmResourceManifestRTTI.h">
       <Filter>Header Files\RTTI</Filter>
       <Filter>Header Files\RTTI</Filter>
     </ClInclude>
     </ClInclude>
+    <ClInclude Include="Include\CmWin32FolderMonitor.h">
+      <Filter>Header Files\Platform</Filter>
+    </ClInclude>
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>
     <ClCompile Include="Source\CmApplication.cpp">
     <ClCompile Include="Source\CmApplication.cpp">
@@ -839,5 +845,8 @@
     <ClCompile Include="Source\CmResourceManifest.cpp">
     <ClCompile Include="Source\CmResourceManifest.cpp">
       <Filter>Source Files\Resources</Filter>
       <Filter>Source Files\Resources</Filter>
     </ClCompile>
     </ClCompile>
+    <ClCompile Include="Source\CmWin32FolderMonitor.cpp">
+      <Filter>Source Files\Platform</Filter>
+    </ClCompile>
   </ItemGroup>
   </ItemGroup>
 </Project>
 </Project>

+ 50 - 0
CamelotCore/Include/CmWin32FolderMonitor.h

@@ -0,0 +1,50 @@
+#pragma once
+
+#include "CmPrerequisites.h"
+
+namespace CamelotFramework
+{
+	enum class FolderChange
+	{
+		FileName = 0x0001,
+		DirName = 0x0002,
+		Attributes = 0x0004,
+		Size = 0x0008,
+		LastWrite = 0x0010,
+		LastAccess = 0x0020,
+		Creation = 0x0040,
+		Security = 0x0080
+	};
+
+	class CM_EXPORT Win32FolderMonitor
+	{
+		struct Pimpl;
+		class FileNotifyInfo;
+		struct FolderWatchInfo;
+	public:
+		Win32FolderMonitor();
+		~Win32FolderMonitor();
+
+		void startMonitor(const WString& folderPath, bool subdirectories, FolderChange changeFilter);
+		void stopMonitor(const WString& folderPath);
+		void stopMonitorAll();
+
+		/**
+		 * @brief	Callbacks will only get fired after update is called().
+		 * 			
+		 * @note	Internal method.
+		 */
+		void update();
+
+		boost::signal<void(const WString&)> onModified;
+		boost::signal<void(const WString&)> onAdded;
+		boost::signal<void(const WString&)> onRemoved;
+		boost::signal<void(const WString&, const WString&)> onRenamed;
+
+	private:
+		Pimpl* mPimpl;
+
+		void workerThreadMain();
+		void handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo);
+	};
+}

+ 2 - 2
CamelotCore/Source/CmImporter.cpp

@@ -53,7 +53,7 @@ namespace CamelotFramework
 
 
 	HResource Importer::import(const WString& inputFilePath, ConstImportOptionsPtr importOptions)
 	HResource Importer::import(const WString& inputFilePath, ConstImportOptionsPtr importOptions)
 	{
 	{
-		if(!FileSystem::fileExists(inputFilePath))
+		if(!FileSystem::isFile(inputFilePath))
 		{
 		{
 			LOGWRN("Trying to import asset that doesn't exists. Asset path: " + toString(inputFilePath));
 			LOGWRN("Trying to import asset that doesn't exists. Asset path: " + toString(inputFilePath));
 			return HResource();
 			return HResource();
@@ -82,7 +82,7 @@ namespace CamelotFramework
 
 
 	ImportOptionsPtr Importer::createImportOptions(const WString& inputFilePath)
 	ImportOptionsPtr Importer::createImportOptions(const WString& inputFilePath)
 	{
 	{
-		if(!FileSystem::fileExists(inputFilePath))
+		if(!FileSystem::isFile(inputFilePath))
 		{
 		{
 			LOGWRN("Trying to import asset that doesn't exists. Asset path: " + toString(inputFilePath));
 			LOGWRN("Trying to import asset that doesn't exists. Asset path: " + toString(inputFilePath));
 			return nullptr;
 			return nullptr;

+ 2 - 2
CamelotCore/Source/CmResources.cpp

@@ -176,7 +176,7 @@ namespace CamelotFramework
 			}
 			}
 		}
 		}
 
 
-		if(!FileSystem::fileExists(filePath))
+		if(!FileSystem::isFile(filePath))
 		{
 		{
 			gDebug().logWarning("Specified file: " + toString(filePath) + " doesn't exist.");
 			gDebug().logWarning("Specified file: " + toString(filePath) + " doesn't exist.");
 			return HResource();
 			return HResource();
@@ -254,7 +254,7 @@ namespace CamelotFramework
 		if(!resource.isLoaded())
 		if(!resource.isLoaded())
 			resource.synchronize();
 			resource.synchronize();
 
 
-		bool fileExists = FileSystem::fileExists(filePath);
+		bool fileExists = FileSystem::isFile(filePath);
 		if(fileExists)
 		if(fileExists)
 		{
 		{
 			if(overwrite)
 			if(overwrite)

+ 652 - 0
CamelotCore/Source/CmWin32FolderMonitor.cpp

@@ -0,0 +1,652 @@
+#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);
+		}
+	}
+}

+ 2 - 2
CamelotUtility/Include/CmFileSystem.h

@@ -15,8 +15,8 @@ namespace CamelotFramework
 		static void remove(const WString& fullPath, bool recursively = true);
 		static void remove(const WString& fullPath, bool recursively = true);
 		static void createDir(const WString& fullPath);
 		static void createDir(const WString& fullPath);
 
 
-		static bool fileExists(const WString& fullPath);
-		static bool dirExists(const WString& fullPath);
+		static bool isFile(const WString& fullPath);
+		static bool isDirectory(const WString& fullPath);
 
 
 		static Vector<WString>::type getFiles(const WString& dirPath);
 		static Vector<WString>::type getFiles(const WString& dirPath);
 
 

+ 16 - 0
CamelotUtility/Include/CmString.h

@@ -78,6 +78,22 @@ namespace CamelotFramework
         */
         */
         static void trim(WString& str, bool left = true, bool right = true);
         static void trim(WString& str, bool left = true, bool right = true);
 
 
+        /** Removes specified characters.
+            @remarks
+                The user may specify wether they want to trim only the
+                beginning or the end of the String ( the default action is
+                to trim both).
+        */
+        static void trim(String& str, const String& delims, bool left = true, bool right = true);
+
+        /** Removes specified characters.
+            @remarks
+                The user may specify wether they want to trim only the
+                beginning or the end of the String ( the default action is
+                to trim both).
+        */
+        static void trim(WString& str, const WString& delims, bool left = true, bool right = true);
+
         /** Returns a StringVector that contains all the substrings delimited
         /** Returns a StringVector that contains all the substrings delimited
             by the characters in the passed <code>delims</code> argument.
             by the characters in the passed <code>delims</code> argument.
             @param
             @param

+ 1 - 1
CamelotUtility/Source/CmDebug.cpp

@@ -34,7 +34,7 @@ namespace CamelotFramework
 
 
 	void Debug::writeAsBMP(UINT8* rawPixels, UINT32 bytesPerPixel, UINT32 width, UINT32 height, const WString& filePath, bool overwrite) const
 	void Debug::writeAsBMP(UINT8* rawPixels, UINT32 bytesPerPixel, UINT32 width, UINT32 height, const WString& filePath, bool overwrite) const
 	{
 	{
-		if(FileSystem::fileExists(filePath))
+		if(FileSystem::isFile(filePath))
 		{
 		{
 			if(overwrite)
 			if(overwrite)
 				FileSystem::remove(filePath);
 				FileSystem::remove(filePath);

+ 2 - 2
CamelotUtility/Source/CmFileSystem.cpp

@@ -82,7 +82,7 @@ namespace CamelotFramework
 			boost::filesystem3::remove(fullPath.c_str());
 			boost::filesystem3::remove(fullPath.c_str());
 	}
 	}
 
 
-	bool FileSystem::fileExists(const WString& fullPath)
+	bool FileSystem::isFile(const WString& fullPath)
 	{
 	{
 		if(exists(fullPath.c_str()) && !is_directory(fullPath.c_str()))
 		if(exists(fullPath.c_str()) && !is_directory(fullPath.c_str()))
 			return true;
 			return true;
@@ -90,7 +90,7 @@ namespace CamelotFramework
 		return false;
 		return false;
 	}
 	}
 
 
-	bool FileSystem::dirExists(const WString& fullPath)
+	bool FileSystem::isDirectory(const WString& fullPath)
 	{
 	{
 		if(exists(fullPath.c_str()) && is_directory(fullPath.c_str()))
 		if(exists(fullPath.c_str()) && is_directory(fullPath.c_str()))
 			return true;
 			return true;

+ 14 - 4
CamelotUtility/Source/CmString.cpp

@@ -45,15 +45,25 @@ namespace CamelotFramework
     void StringUtil::trim(String& str, bool left, bool right)
     void StringUtil::trim(String& str, bool left, bool right)
     {
     {
         static const String delims = " \t\r";
         static const String delims = " \t\r";
-        if(right)
-            str.erase(str.find_last_not_of(delims)+1); // trim right
-        if(left)
-            str.erase(0, str.find_first_not_of(delims)); // trim left
+        trim(str, delims, left, right);
     }
     }
 
 
 	void StringUtil::trim(WString& str, bool left, bool right)
 	void StringUtil::trim(WString& str, bool left, bool right)
 	{
 	{
 		static const WString delims = L" \t\r";
 		static const WString delims = L" \t\r";
+		trim(str, delims, left, right);
+	}
+
+	void StringUtil::trim(String& str, const String& delims, bool left, bool right)
+	{
+		if(right)
+			str.erase(str.find_last_not_of(delims)+1); // trim right
+		if(left)
+			str.erase(0, str.find_first_not_of(delims)); // trim left
+	}
+
+	void StringUtil::trim(WString& str, const WString& delims, bool left, bool right)
+	{
 		if(right)
 		if(right)
 			str.erase(str.find_last_not_of(delims)+1); // trim right
 			str.erase(str.find_last_not_of(delims)+1); // trim right
 		if(left)
 		if(left)