Parcourir la source

Don't complain when a file monitor tries to access a deleted file

BearishSun il y a 10 ans
Parent
commit
4c13580146
1 fichiers modifiés avec 696 ajouts et 693 suppressions
  1. 696 693
      BansheeCore/Source/Win32/BsWin32FolderMonitor.cpp

+ 696 - 693
BansheeCore/Source/Win32/BsWin32FolderMonitor.cpp

@@ -1,694 +1,697 @@
-#include "Win32/BsWin32FolderMonitor.h"
-#include "BsFileSystem.h"
-#include "BsException.h"
-
-#include "BsDebug.h"
-
-#include <windows.h>
-
-namespace BansheeEngine
-{
-	enum class MonitorState
-	{
-		Inactive,
-		Starting,
-		Monitoring,
-		Shutdown,
-		Shutdown2
-	};
-
-	class WorkerFunc
-	{
-	public:
-		WorkerFunc(FolderMonitor* owner);
-
-		void operator()();
-
-	private:
-		FolderMonitor* mOwner;
-	};
-
-	struct FolderMonitor::FolderWatchInfo
-	{
-		FolderWatchInfo(const Path& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags);
-		~FolderWatchInfo();
-
-		void startMonitor(HANDLE compPortHandle);
-		void stopMonitor(HANDLE compPortHandle);
-
-		static const UINT32 READ_BUFFER_SIZE = 65536;
-
-		Path 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
-
-		BS_MUTEX(mStatusMutex)
-		BS_THREAD_SYNCHRONISER(mStartStopEvent)
-	};
-
-	FolderMonitor::FolderWatchInfo::FolderWatchInfo(const Path& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags)
-		:mFolderToMonitor(folderToMonitor), mDirHandle(dirHandle), mState(MonitorState::Inactive), mBufferSize(0),
-		mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags), mReadError(0)
-	{
-		memset(&mOverlapped, 0, sizeof(mOverlapped));
-	}
-
-	FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
-	{
-		assert(mState == MonitorState::Inactive);
-
-		stopMonitor(0);
-	}
-
-	void FolderMonitor::FolderWatchInfo::startMonitor(HANDLE compPortHandle)
-	{
-		if(mState != MonitorState::Inactive)
-			return; // Already monitoring
-
-		{
-			BS_LOCK_MUTEX_NAMED(mStatusMutex, lock);
-
-			mState = MonitorState::Starting;
-			PostQueuedCompletionStatus(compPortHandle, sizeof(this), (ULONG_PTR)this, &mOverlapped);
-
-			while(mState != MonitorState::Monitoring)
-				BS_THREAD_WAIT(mStartStopEvent, mStatusMutex, lock);
-		}
-
-		if(mReadError != ERROR_SUCCESS)
-		{
-			{
-				BS_LOCK_MUTEX(mStatusMutex);
-				mState = MonitorState::Inactive;
-			}
-
-			BS_EXCEPT(InternalErrorException, "Failed to start folder monitor on folder \"" + 
-				mFolderToMonitor.toString() + "\" because ReadDirectoryChangesW failed.");
-		}
-	}
-
-	void FolderMonitor::FolderWatchInfo::stopMonitor(HANDLE compPortHandle)
-	{
-		if(mState != MonitorState::Inactive)
-		{
-			BS_LOCK_MUTEX_NAMED(mStatusMutex, lock);
-
-			mState = MonitorState::Shutdown;
-			PostQueuedCompletionStatus(compPortHandle, sizeof(this), (ULONG_PTR)this, &mOverlapped);
-
-			while(mState != MonitorState::Inactive)
-				BS_THREAD_WAIT(mStartStopEvent, mStatusMutex, lock);
-		}
-
-		if(mDirHandle != INVALID_HANDLE_VALUE)
-		{			
-			CloseHandle(mDirHandle);
-			mDirHandle = INVALID_HANDLE_VALUE;
-		}
-	}
-
-	class FolderMonitor::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 Path& rootPath) const;
-
-	protected:
-		UINT8* mBuffer;
-		DWORD mBufferSize;
-		PFILE_NOTIFY_INFORMATION mCurrentRecord;
-	};
-
-	bool FolderMonitor::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 FolderMonitor::FileNotifyInfo::getAction() const
-	{ 
-		assert(mCurrentRecord != nullptr);
-
-		if(mCurrentRecord)
-			return mCurrentRecord->Action;
-
-		return 0;
-	}
-
-	WString FolderMonitor::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 FolderMonitor::FileNotifyInfo::getFileNameWithPath(const Path& rootPath) const
-	{
-		Path fullPath = rootPath;
-		return fullPath.append(getFileName()).toWString();
-	}
-
-	enum class FileActionType
-	{
-		Added,
-		Removed,
-		Modified,
-		Renamed
-	};
-
-	struct FileAction
-	{
-		static FileAction* createAdded(const WString& fileName)
-		{
-			UINT8* bytes = (UINT8*)bs_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';
-			action->lastSize = 0;
-			action->checkForWriteStarted = false;
-
-			return action;
-		}
-
-		static FileAction* createRemoved(const WString& fileName)
-		{
-			UINT8* bytes = (UINT8*)bs_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';
-			action->lastSize = 0;
-			action->checkForWriteStarted = false;
-
-			return action;
-		}
-
-		static FileAction* createModified(const WString& fileName)
-		{
-			UINT8* bytes = (UINT8*)bs_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';
-			action->lastSize = 0;
-			action->checkForWriteStarted = false;
-
-			return action;
-		}
-
-		static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
-		{
-			UINT8* bytes = (UINT8*)bs_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';
-			action->lastSize = 0;
-			action->checkForWriteStarted = false;
-
-			return action;
-		}
-
-		static void destroy(FileAction* action)
-		{
-			bs_free(action);
-		}
-
-		WString::value_type* oldName;
-		WString::value_type* newName;
-		FileActionType type;
-
-		UINT64 lastSize;
-		bool checkForWriteStarted;
-	};
-
-	struct FolderMonitor::Pimpl
-	{
-		Vector<FolderWatchInfo*> mFoldersToWatch;
-		HANDLE mCompPortHandle;
-
-		Queue<FileAction*> mFileActions;
-		List<FileAction*> mActiveFileActions;
-
-		BS_MUTEX(mMainMutex);
-		BS_THREAD_TYPE* mWorkerThread;
-	};
-
-	FolderMonitor::FolderMonitor()
-	{
-		mPimpl = bs_new<Pimpl>();
-		mPimpl->mWorkerThread = nullptr;
-		mPimpl->mCompPortHandle = nullptr;
-	}
-
-	FolderMonitor::~FolderMonitor()
-	{
-		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);
-		}
-
-		bs_delete(mPimpl);
-	}
-
-	void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChange changeFilter)
-	{
-		if(!FileSystem::isDirectory(folderPath))
-		{
-			BS_EXCEPT(InvalidParametersException, "Provided path \"" + folderPath.toString() + "\" is not a directory");
-		}
-
-		WString extendedFolderPath = L"\\\\?\\" + folderPath.toWString(Path::PathType::Windows);
-		HANDLE dirHandle = CreateFileW(extendedFolderPath.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)
-		{
-			BS_EXCEPT(InternalErrorException, "Failed to open folder \"" + folderPath.toString() + "\" for monitoring. Error code: " + toString((UINT64)GetLastError()));
-		}
-
-		DWORD filterFlags = 0;
-
-		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::FileName) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_FILE_NAME;
-
-		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::DirName) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_DIR_NAME;
-
-		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Attributes) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
-
-		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Size) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_SIZE;
-
-		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::LastWrite) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_LAST_WRITE;
-
-		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::LastAccess) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
-
-		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Creation) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_CREATION;
-
-		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Security) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_SECURITY;
-
-		mPimpl->mFoldersToWatch.push_back(bs_new<FolderWatchInfo>(folderPath, dirHandle, subdirectories, filterFlags));
-		FolderWatchInfo* watchInfo = mPimpl->mFoldersToWatch.back();
-
-		mPimpl->mCompPortHandle = CreateIoCompletionPort(dirHandle, mPimpl->mCompPortHandle, (ULONG_PTR)watchInfo, 0);
-
-		if(mPimpl->mCompPortHandle == nullptr)
-		{
-			mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
-			bs_delete(watchInfo);
-			BS_EXCEPT(InternalErrorException, "Failed to open completition port for folder monitoring. Error code: " + toString((UINT64)GetLastError()));
-		}
-
-		if(mPimpl->mWorkerThread == nullptr)
-		{
-			BS_THREAD_CREATE(t, (std::bind(&FolderMonitor::workerThreadMain, this)));
-			mPimpl->mWorkerThread = t;
-
-			if(mPimpl->mWorkerThread == nullptr)
-			{
-				mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
-				bs_delete(watchInfo);
-				BS_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);
-				bs_delete(watchInfo);
-				throw(e);
-			}
-		}
-		else
-		{
-			mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
-			bs_delete(watchInfo);
-			BS_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
-		}
-	}
-
-	void FolderMonitor::stopMonitor(const Path& folderPath)
-	{
-		auto findIter = std::find_if(mPimpl->mFoldersToWatch.begin(), mPimpl->mFoldersToWatch.end(), 
-			[&](const FolderWatchInfo* x) { return x->mFolderToMonitor == folderPath; });
-
-		if(findIter != mPimpl->mFoldersToWatch.end())
-		{
-			FolderWatchInfo* watchInfo = *findIter;
-
-			watchInfo->stopMonitor(mPimpl->mCompPortHandle);
-			bs_delete(watchInfo);
-
-			mPimpl->mFoldersToWatch.erase(findIter);
-		}
-
-		if(mPimpl->mFoldersToWatch.size() == 0)
-			stopMonitorAll();
-	}
-
-	void FolderMonitor::stopMonitorAll()
-	{
-		for(auto& watchInfo : mPimpl->mFoldersToWatch)
-		{
-			watchInfo->stopMonitor(mPimpl->mCompPortHandle);
-
-			{
-				// Note: Need this mutex to ensure worker thread is done with watchInfo.
-				// Even though we wait for a condition variable from the worker thread in stopMonitor,
-				// that doesn't mean the worker thread is done with the condition variable
-				// (which is stored inside watchInfo)
-				BS_LOCK_MUTEX(mPimpl->mMainMutex); 
-				bs_delete(watchInfo);
-			}
-		}
-
-		mPimpl->mFoldersToWatch.clear();
-
-		if(mPimpl->mWorkerThread != nullptr)
-		{
-			PostQueuedCompletionStatus(mPimpl->mCompPortHandle, 0, 0, nullptr);
-
-			mPimpl->mWorkerThread->join();
-			BS_THREAD_DESTROY(mPimpl->mWorkerThread);
-			mPimpl->mWorkerThread = nullptr;
-		}
-
-		if(mPimpl->mCompPortHandle != nullptr)
-		{
-			CloseHandle(mPimpl->mCompPortHandle);
-			mPimpl->mCompPortHandle = nullptr;
-		}
-	}
-
-	void FolderMonitor::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;
-
-				{
-					BS_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;
-
-						{
-							BS_LOCK_MUTEX(watchInfo->mStatusMutex);
-							watchInfo->mState = MonitorState::Monitoring;
-						}
-					}
-
-					BS_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;
-
-						{
-							BS_LOCK_MUTEX(watchInfo->mStatusMutex);
-							watchInfo->mState = MonitorState::Shutdown2;
-						}
-					}
-					else
-					{
-						{
-							BS_LOCK_MUTEX(watchInfo->mStatusMutex);
-							watchInfo->mState = MonitorState::Inactive;
-						}
-
-						{
-							BS_LOCK_MUTEX(mPimpl->mMainMutex); // Ensures that we don't delete "watchInfo" before this thread is done with mStartStopEvent
-							BS_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
-					{
-						{
-							BS_LOCK_MUTEX(watchInfo->mStatusMutex);
-							watchInfo->mState = MonitorState::Inactive;
-						}
-
-						{
-							BS_LOCK_MUTEX(mPimpl->mMainMutex); // Ensures that we don't delete "watchInfo" before this thread is done with mStartStopEvent
-							BS_THREAD_NOTIFY_ONE(watchInfo->mStartStopEvent);
-						}
-					}
-
-					break;
-				}
-			}
-
-		} while (watchInfo != nullptr);
-	}
-
-	void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
-	{
-		Vector<FileAction*> mActions;
-
-		do
-		{
-			WString fullPath = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
-
-			// Ignore notifications about hidden files
-			if ((GetFileAttributesW(fullPath.c_str()) & FILE_ATTRIBUTE_HIDDEN) != 0)
-				continue;
-
-			switch(notifyInfo.getAction())
-			{
-			case FILE_ACTION_ADDED:
-					mActions.push_back(FileAction::createAdded(fullPath));
-				break;
-			case FILE_ACTION_REMOVED:
-					mActions.push_back(FileAction::createRemoved(fullPath));
-				break;
-			case FILE_ACTION_MODIFIED:
-					mActions.push_back(FileAction::createModified(fullPath));
-				break;
-			case FILE_ACTION_RENAMED_OLD_NAME:
-					watchInfo.mCachedOldFileName = fullPath;
-				break;
-			case FILE_ACTION_RENAMED_NEW_NAME:
-					mActions.push_back(FileAction::createRenamed(watchInfo.mCachedOldFileName, fullPath));
-				break;
-			}
-    
-		} while(notifyInfo.getNext());
-
-		{
-			BS_LOCK_MUTEX(mPimpl->mMainMutex);
-
-			for(auto& action : mActions)
-				mPimpl->mFileActions.push(action);
-		}
-	}
-
-	void FolderMonitor::_update()
-	{
-		{
-			BS_LOCK_MUTEX(mPimpl->mMainMutex);
-
-			while (!mPimpl->mFileActions.empty())
-			{
-				FileAction* action = mPimpl->mFileActions.front();
-				mPimpl->mFileActions.pop();
-
-				mPimpl->mActiveFileActions.push_back(action);
-			}
-		}
-
-		for (auto iter = mPimpl->mActiveFileActions.begin(); iter != mPimpl->mActiveFileActions.end();)
-		{
-			FileAction* action = *iter;
-			
-			// Reported file actions might still be in progress (i.e. something might still be writing to those files).
-			// Sadly there doesn't seem to be a way to properly determine when those files are done being written, so instead
-			// we check for at least a couple of frames if the file's size hasn't changed before reporting a file action.
-			// This takes care of most of the issues and avoids reporting partially written files in almost all cases.
-			UINT64 size = FileSystem::getFileSize(action->newName);
-			if (!action->checkForWriteStarted)
-			{
-				action->checkForWriteStarted = true;
-				action->lastSize = size;
-
-				++iter;
-				continue;
-			}
-			else
-			{
-				if (action->lastSize != size)
-				{
-					action->lastSize = size;
-					++iter;
-					continue;
-				}
-			}
-
-			switch (action->type)
-			{
-			case FileActionType::Added:
-				if (!onAdded.empty())
-					onAdded(Path(action->newName));
-				break;
-			case FileActionType::Removed:
-				if (!onRemoved.empty())
-					onRemoved(Path(action->newName));
-				break;
-			case FileActionType::Modified:
-				if (!onModified.empty())
-					onModified(Path(action->newName));
-				break;
-			case FileActionType::Renamed:
-				if (!onRenamed.empty())
-					onRenamed(Path(action->oldName), Path(action->newName));
-				break;
-			}
-
-			mPimpl->mActiveFileActions.erase(iter++);
-			FileAction::destroy(action);
-		}
-	}
+#include "Win32/BsWin32FolderMonitor.h"
+#include "BsFileSystem.h"
+#include "BsException.h"
+
+#include "BsDebug.h"
+
+#include <windows.h>
+
+namespace BansheeEngine
+{
+	enum class MonitorState
+	{
+		Inactive,
+		Starting,
+		Monitoring,
+		Shutdown,
+		Shutdown2
+	};
+
+	class WorkerFunc
+	{
+	public:
+		WorkerFunc(FolderMonitor* owner);
+
+		void operator()();
+
+	private:
+		FolderMonitor* mOwner;
+	};
+
+	struct FolderMonitor::FolderWatchInfo
+	{
+		FolderWatchInfo(const Path& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags);
+		~FolderWatchInfo();
+
+		void startMonitor(HANDLE compPortHandle);
+		void stopMonitor(HANDLE compPortHandle);
+
+		static const UINT32 READ_BUFFER_SIZE = 65536;
+
+		Path 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
+
+		BS_MUTEX(mStatusMutex)
+		BS_THREAD_SYNCHRONISER(mStartStopEvent)
+	};
+
+	FolderMonitor::FolderWatchInfo::FolderWatchInfo(const Path& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags)
+		:mFolderToMonitor(folderToMonitor), mDirHandle(dirHandle), mState(MonitorState::Inactive), mBufferSize(0),
+		mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags), mReadError(0)
+	{
+		memset(&mOverlapped, 0, sizeof(mOverlapped));
+	}
+
+	FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
+	{
+		assert(mState == MonitorState::Inactive);
+
+		stopMonitor(0);
+	}
+
+	void FolderMonitor::FolderWatchInfo::startMonitor(HANDLE compPortHandle)
+	{
+		if(mState != MonitorState::Inactive)
+			return; // Already monitoring
+
+		{
+			BS_LOCK_MUTEX_NAMED(mStatusMutex, lock);
+
+			mState = MonitorState::Starting;
+			PostQueuedCompletionStatus(compPortHandle, sizeof(this), (ULONG_PTR)this, &mOverlapped);
+
+			while(mState != MonitorState::Monitoring)
+				BS_THREAD_WAIT(mStartStopEvent, mStatusMutex, lock);
+		}
+
+		if(mReadError != ERROR_SUCCESS)
+		{
+			{
+				BS_LOCK_MUTEX(mStatusMutex);
+				mState = MonitorState::Inactive;
+			}
+
+			BS_EXCEPT(InternalErrorException, "Failed to start folder monitor on folder \"" + 
+				mFolderToMonitor.toString() + "\" because ReadDirectoryChangesW failed.");
+		}
+	}
+
+	void FolderMonitor::FolderWatchInfo::stopMonitor(HANDLE compPortHandle)
+	{
+		if(mState != MonitorState::Inactive)
+		{
+			BS_LOCK_MUTEX_NAMED(mStatusMutex, lock);
+
+			mState = MonitorState::Shutdown;
+			PostQueuedCompletionStatus(compPortHandle, sizeof(this), (ULONG_PTR)this, &mOverlapped);
+
+			while(mState != MonitorState::Inactive)
+				BS_THREAD_WAIT(mStartStopEvent, mStatusMutex, lock);
+		}
+
+		if(mDirHandle != INVALID_HANDLE_VALUE)
+		{			
+			CloseHandle(mDirHandle);
+			mDirHandle = INVALID_HANDLE_VALUE;
+		}
+	}
+
+	class FolderMonitor::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 Path& rootPath) const;
+
+	protected:
+		UINT8* mBuffer;
+		DWORD mBufferSize;
+		PFILE_NOTIFY_INFORMATION mCurrentRecord;
+	};
+
+	bool FolderMonitor::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 FolderMonitor::FileNotifyInfo::getAction() const
+	{ 
+		assert(mCurrentRecord != nullptr);
+
+		if(mCurrentRecord)
+			return mCurrentRecord->Action;
+
+		return 0;
+	}
+
+	WString FolderMonitor::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 FolderMonitor::FileNotifyInfo::getFileNameWithPath(const Path& rootPath) const
+	{
+		Path fullPath = rootPath;
+		return fullPath.append(getFileName()).toWString();
+	}
+
+	enum class FileActionType
+	{
+		Added,
+		Removed,
+		Modified,
+		Renamed
+	};
+
+	struct FileAction
+	{
+		static FileAction* createAdded(const WString& fileName)
+		{
+			UINT8* bytes = (UINT8*)bs_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';
+			action->lastSize = 0;
+			action->checkForWriteStarted = false;
+
+			return action;
+		}
+
+		static FileAction* createRemoved(const WString& fileName)
+		{
+			UINT8* bytes = (UINT8*)bs_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';
+			action->lastSize = 0;
+			action->checkForWriteStarted = false;
+
+			return action;
+		}
+
+		static FileAction* createModified(const WString& fileName)
+		{
+			UINT8* bytes = (UINT8*)bs_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';
+			action->lastSize = 0;
+			action->checkForWriteStarted = false;
+
+			return action;
+		}
+
+		static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
+		{
+			UINT8* bytes = (UINT8*)bs_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';
+			action->lastSize = 0;
+			action->checkForWriteStarted = false;
+
+			return action;
+		}
+
+		static void destroy(FileAction* action)
+		{
+			bs_free(action);
+		}
+
+		WString::value_type* oldName;
+		WString::value_type* newName;
+		FileActionType type;
+
+		UINT64 lastSize;
+		bool checkForWriteStarted;
+	};
+
+	struct FolderMonitor::Pimpl
+	{
+		Vector<FolderWatchInfo*> mFoldersToWatch;
+		HANDLE mCompPortHandle;
+
+		Queue<FileAction*> mFileActions;
+		List<FileAction*> mActiveFileActions;
+
+		BS_MUTEX(mMainMutex);
+		BS_THREAD_TYPE* mWorkerThread;
+	};
+
+	FolderMonitor::FolderMonitor()
+	{
+		mPimpl = bs_new<Pimpl>();
+		mPimpl->mWorkerThread = nullptr;
+		mPimpl->mCompPortHandle = nullptr;
+	}
+
+	FolderMonitor::~FolderMonitor()
+	{
+		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);
+		}
+
+		bs_delete(mPimpl);
+	}
+
+	void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChange changeFilter)
+	{
+		if(!FileSystem::isDirectory(folderPath))
+		{
+			BS_EXCEPT(InvalidParametersException, "Provided path \"" + folderPath.toString() + "\" is not a directory");
+		}
+
+		WString extendedFolderPath = L"\\\\?\\" + folderPath.toWString(Path::PathType::Windows);
+		HANDLE dirHandle = CreateFileW(extendedFolderPath.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)
+		{
+			BS_EXCEPT(InternalErrorException, "Failed to open folder \"" + folderPath.toString() + "\" for monitoring. Error code: " + toString((UINT64)GetLastError()));
+		}
+
+		DWORD filterFlags = 0;
+
+		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::FileName) != 0)
+			filterFlags |= FILE_NOTIFY_CHANGE_FILE_NAME;
+
+		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::DirName) != 0)
+			filterFlags |= FILE_NOTIFY_CHANGE_DIR_NAME;
+
+		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Attributes) != 0)
+			filterFlags |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
+
+		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Size) != 0)
+			filterFlags |= FILE_NOTIFY_CHANGE_SIZE;
+
+		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::LastWrite) != 0)
+			filterFlags |= FILE_NOTIFY_CHANGE_LAST_WRITE;
+
+		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::LastAccess) != 0)
+			filterFlags |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
+
+		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Creation) != 0)
+			filterFlags |= FILE_NOTIFY_CHANGE_CREATION;
+
+		if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Security) != 0)
+			filterFlags |= FILE_NOTIFY_CHANGE_SECURITY;
+
+		mPimpl->mFoldersToWatch.push_back(bs_new<FolderWatchInfo>(folderPath, dirHandle, subdirectories, filterFlags));
+		FolderWatchInfo* watchInfo = mPimpl->mFoldersToWatch.back();
+
+		mPimpl->mCompPortHandle = CreateIoCompletionPort(dirHandle, mPimpl->mCompPortHandle, (ULONG_PTR)watchInfo, 0);
+
+		if(mPimpl->mCompPortHandle == nullptr)
+		{
+			mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
+			bs_delete(watchInfo);
+			BS_EXCEPT(InternalErrorException, "Failed to open completition port for folder monitoring. Error code: " + toString((UINT64)GetLastError()));
+		}
+
+		if(mPimpl->mWorkerThread == nullptr)
+		{
+			BS_THREAD_CREATE(t, (std::bind(&FolderMonitor::workerThreadMain, this)));
+			mPimpl->mWorkerThread = t;
+
+			if(mPimpl->mWorkerThread == nullptr)
+			{
+				mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
+				bs_delete(watchInfo);
+				BS_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);
+				bs_delete(watchInfo);
+				throw(e);
+			}
+		}
+		else
+		{
+			mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
+			bs_delete(watchInfo);
+			BS_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
+		}
+	}
+
+	void FolderMonitor::stopMonitor(const Path& folderPath)
+	{
+		auto findIter = std::find_if(mPimpl->mFoldersToWatch.begin(), mPimpl->mFoldersToWatch.end(), 
+			[&](const FolderWatchInfo* x) { return x->mFolderToMonitor == folderPath; });
+
+		if(findIter != mPimpl->mFoldersToWatch.end())
+		{
+			FolderWatchInfo* watchInfo = *findIter;
+
+			watchInfo->stopMonitor(mPimpl->mCompPortHandle);
+			bs_delete(watchInfo);
+
+			mPimpl->mFoldersToWatch.erase(findIter);
+		}
+
+		if(mPimpl->mFoldersToWatch.size() == 0)
+			stopMonitorAll();
+	}
+
+	void FolderMonitor::stopMonitorAll()
+	{
+		for(auto& watchInfo : mPimpl->mFoldersToWatch)
+		{
+			watchInfo->stopMonitor(mPimpl->mCompPortHandle);
+
+			{
+				// Note: Need this mutex to ensure worker thread is done with watchInfo.
+				// Even though we wait for a condition variable from the worker thread in stopMonitor,
+				// that doesn't mean the worker thread is done with the condition variable
+				// (which is stored inside watchInfo)
+				BS_LOCK_MUTEX(mPimpl->mMainMutex); 
+				bs_delete(watchInfo);
+			}
+		}
+
+		mPimpl->mFoldersToWatch.clear();
+
+		if(mPimpl->mWorkerThread != nullptr)
+		{
+			PostQueuedCompletionStatus(mPimpl->mCompPortHandle, 0, 0, nullptr);
+
+			mPimpl->mWorkerThread->join();
+			BS_THREAD_DESTROY(mPimpl->mWorkerThread);
+			mPimpl->mWorkerThread = nullptr;
+		}
+
+		if(mPimpl->mCompPortHandle != nullptr)
+		{
+			CloseHandle(mPimpl->mCompPortHandle);
+			mPimpl->mCompPortHandle = nullptr;
+		}
+	}
+
+	void FolderMonitor::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;
+
+				{
+					BS_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;
+
+						{
+							BS_LOCK_MUTEX(watchInfo->mStatusMutex);
+							watchInfo->mState = MonitorState::Monitoring;
+						}
+					}
+
+					BS_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;
+
+						{
+							BS_LOCK_MUTEX(watchInfo->mStatusMutex);
+							watchInfo->mState = MonitorState::Shutdown2;
+						}
+					}
+					else
+					{
+						{
+							BS_LOCK_MUTEX(watchInfo->mStatusMutex);
+							watchInfo->mState = MonitorState::Inactive;
+						}
+
+						{
+							BS_LOCK_MUTEX(mPimpl->mMainMutex); // Ensures that we don't delete "watchInfo" before this thread is done with mStartStopEvent
+							BS_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
+					{
+						{
+							BS_LOCK_MUTEX(watchInfo->mStatusMutex);
+							watchInfo->mState = MonitorState::Inactive;
+						}
+
+						{
+							BS_LOCK_MUTEX(mPimpl->mMainMutex); // Ensures that we don't delete "watchInfo" before this thread is done with mStartStopEvent
+							BS_THREAD_NOTIFY_ONE(watchInfo->mStartStopEvent);
+						}
+					}
+
+					break;
+				}
+			}
+
+		} while (watchInfo != nullptr);
+	}
+
+	void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
+	{
+		Vector<FileAction*> mActions;
+
+		do
+		{
+			WString fullPath = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
+
+			// Ignore notifications about hidden files
+			if ((GetFileAttributesW(fullPath.c_str()) & FILE_ATTRIBUTE_HIDDEN) != 0)
+				continue;
+
+			switch(notifyInfo.getAction())
+			{
+			case FILE_ACTION_ADDED:
+					mActions.push_back(FileAction::createAdded(fullPath));
+				break;
+			case FILE_ACTION_REMOVED:
+					mActions.push_back(FileAction::createRemoved(fullPath));
+				break;
+			case FILE_ACTION_MODIFIED:
+					mActions.push_back(FileAction::createModified(fullPath));
+				break;
+			case FILE_ACTION_RENAMED_OLD_NAME:
+					watchInfo.mCachedOldFileName = fullPath;
+				break;
+			case FILE_ACTION_RENAMED_NEW_NAME:
+					mActions.push_back(FileAction::createRenamed(watchInfo.mCachedOldFileName, fullPath));
+				break;
+			}
+    
+		} while(notifyInfo.getNext());
+
+		{
+			BS_LOCK_MUTEX(mPimpl->mMainMutex);
+
+			for(auto& action : mActions)
+				mPimpl->mFileActions.push(action);
+		}
+	}
+
+	void FolderMonitor::_update()
+	{
+		{
+			BS_LOCK_MUTEX(mPimpl->mMainMutex);
+
+			while (!mPimpl->mFileActions.empty())
+			{
+				FileAction* action = mPimpl->mFileActions.front();
+				mPimpl->mFileActions.pop();
+
+				mPimpl->mActiveFileActions.push_back(action);
+			}
+		}
+
+		for (auto iter = mPimpl->mActiveFileActions.begin(); iter != mPimpl->mActiveFileActions.end();)
+		{
+			FileAction* action = *iter;
+			
+			// Reported file actions might still be in progress (i.e. something might still be writing to those files).
+			// Sadly there doesn't seem to be a way to properly determine when those files are done being written, so instead
+			// we check for at least a couple of frames if the file's size hasn't changed before reporting a file action.
+			// This takes care of most of the issues and avoids reporting partially written files in almost all cases.
+			if (FileSystem::exists(action->newName))
+			{
+				UINT64 size = FileSystem::getFileSize(action->newName);
+				if (!action->checkForWriteStarted)
+				{
+					action->checkForWriteStarted = true;
+					action->lastSize = size;
+
+					++iter;
+					continue;
+				}
+				else
+				{
+					if (action->lastSize != size)
+					{
+						action->lastSize = size;
+						++iter;
+						continue;
+					}
+				}
+			}
+
+			switch (action->type)
+			{
+			case FileActionType::Added:
+				if (!onAdded.empty())
+					onAdded(Path(action->newName));
+				break;
+			case FileActionType::Removed:
+				if (!onRemoved.empty())
+					onRemoved(Path(action->newName));
+				break;
+			case FileActionType::Modified:
+				if (!onModified.empty())
+					onModified(Path(action->newName));
+				break;
+			case FileActionType::Renamed:
+				if (!onRenamed.empty())
+					onRenamed(Path(action->oldName), Path(action->newName));
+				break;
+			}
+
+			mPimpl->mActiveFileActions.erase(iter++);
+			FileAction::destroy(action);
+		}
+	}
 }