Parcourir la source

WIP: Linux port
- Folder monitor

BearishSun il y a 8 ans
Parent
commit
f86bbde07b

+ 0 - 1
Source/BansheeCore/CMakeSources.cmake

@@ -580,7 +580,6 @@ set(BS_BANSHEECORE_INC_PLATFORM
 )
 )
 
 
 set(BS_BANSHEECORE_INC_PLATFORM_WIN32
 set(BS_BANSHEECORE_INC_PLATFORM_WIN32
-	"Win32/BsWin32FolderMonitor.h"
 	"Win32/BsWin32DropTarget.h"
 	"Win32/BsWin32DropTarget.h"
 	"Win32/BsWin32Defs.h"
 	"Win32/BsWin32Defs.h"
 	"Win32/BSWin32PlatformData.h"
 	"Win32/BSWin32PlatformData.h"

+ 76 - 3
Source/BansheeCore/Platform/BsFolderMonitor.h

@@ -4,6 +4,79 @@
 
 
 #include "BsCorePrerequisites.h"
 #include "BsCorePrerequisites.h"
 
 
-#if BS_PLATFORM == BS_PLATFORM_WIN32
-#include "Win32/BsWin32FolderMonitor.h"
-#endif
+namespace bs
+{
+	/** @addtogroup Platform-Internal
+	 *  @{
+	 */
+
+	/** Types of notifications we would like to receive when we start a FolderMonitor on a certain folder. */
+	enum class FolderChange
+	{
+		FileName = 0x0001, /**< Called when filename changes. */
+		DirName = 0x0002, /**< Called when directory name changes. */
+		Attributes = 0x0004, /**< Called when attributes changes. */
+		Size = 0x0008, /**< Called when file size changes. */
+		LastWrite = 0x0010, /**< Called when file is written to. */
+		LastAccess = 0x0020, /**< Called when file is accessed. */
+		Creation = 0x0040, /**< Called when file is created. */
+		Security = 0x0080 /**< Called when file security descriptor changes. */
+	};
+
+	/**
+	 * Allows monitoring a file system folder for changes. Depending on the flags set this monitor can notify you when file
+	 * is changed/moved/renamed and similar.
+	 */
+	class BS_CORE_EXPORT FolderMonitor
+	{
+		struct Pimpl;
+		class FileNotifyInfo;
+		struct FolderWatchInfo;
+	public:
+		FolderMonitor();
+		~FolderMonitor();
+
+		/**
+		 * Starts monitoring a folder at the specified path.
+		 *
+		 * @param[in]	folderPath		Absolute path to the folder you want to monitor.
+		 * @param[in]	subdirectories	If true, provided folder and all of its subdirectories will be monitored for 
+		 *								changes. Otherwise only the provided folder will be monitored.
+		 * @param[in]	changeFilter	A set of flags you may OR together. Different notification events will trigger 
+		 *								depending on which flags you set.
+		 */
+		void startMonitor(const Path& folderPath, bool subdirectories, FolderChange changeFilter);
+
+		/** Stops monitoring the folder at the specified path. */
+		void stopMonitor(const Path& folderPath);
+
+		/**	Stops monitoring all folders that are currently being monitored. */
+		void stopMonitorAll();
+
+		/** Callbacks will only get fired after update is called. */
+		void _update();
+
+		/** Triggers when a file in the monitored folder is modified. Provides absolute path to the file. */
+		Event<void(const Path&)> onModified;
+
+		/**	Triggers when a file/folder is added in the monitored folder. Provides absolute path to the file/folder. */
+		Event<void(const Path&)> onAdded;
+
+		/**	Triggers when a file/folder is removed from the monitored folder. Provides absolute path to the file/folder. */
+		Event<void(const Path&)> onRemoved;
+
+		/**	Triggers when a file/folder is renamed in the monitored folder. Provides absolute path with old and new names. */
+		Event<void(const Path&, const Path&)> onRenamed;
+
+	private:
+		/**	Worker method that monitors the IO ports for any modification notifications. */
+		void workerThreadMain();
+
+		/**	Called by the worker thread whenever a modification notification is received. */
+		void handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo);
+
+		Pimpl* mPimpl;
+	};
+
+	/** @} */
+}

+ 386 - 0
Source/BansheeCore/Win32/BsLinuxFolderMonitor.cpp

@@ -0,0 +1,386 @@
+//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
+//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
+#include "Platform/BsFolderMonitor.h"
+#include "FileSystem/BsFileSystem.h"
+#include "Error/BsException.h"
+#include <corecrt_io.h>
+
+namespace bs
+{
+	class WorkerFunc
+	{
+	public:
+		WorkerFunc(FolderMonitor* owner);
+
+		void operator()();
+
+	private:
+		FolderMonitor* mOwner;
+	};
+
+	struct FolderMonitor::FolderWatchInfo
+	{
+		FolderWatchInfo(const Path& folderToMonitor, int inHandle, bool monitorSubdirectories, UINT32 monitorFlags);
+		~FolderWatchInfo();
+
+		void startMonitor();
+		void stopMonitor();
+
+		Path mFolderToMonitor;
+		int mDirHandle;
+		bool mMonitorSubdirectories;
+		UINT32 mMonitorFlags;
+	};
+
+	FolderMonitor::FolderWatchInfo::FolderWatchInfo(const Path& folderToMonitor, int inHandle, bool monitorSubdirectories, UINT32 monitorFlags)
+		:mFolderToMonitor(folderToMonitor), mDirHandle(0), mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags)
+	{ }
+
+	FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
+	{
+		stopMonitor();
+	}
+
+	void FolderMonitor::FolderWatchInfo::startMonitor()
+	{
+	}
+
+	void FolderMonitor::FolderWatchInfo::stopMonitor()
+	{
+	}
+
+	class FolderMonitor::FileNotifyInfo
+	{
+	};
+
+	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;
+
+		Queue<FileAction*> mFileActions;
+		List<FileAction*> mActiveFileActions;
+
+		int inHandle;
+		Mutex mMainMutex;
+		Thread* mWorkerThread;
+	};
+
+	FolderMonitor::FolderMonitor()
+	{
+		mPimpl = bs_new<Pimpl>();
+		mPimpl->mWorkerThread = nullptr;
+		mPimpl->inHandle = 0;
+	}
+
+	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))
+		{
+			LOGERR("Provided path \"" + folderPath.toString() + "\" is not a directory");
+			return;
+		}
+
+		UINT32 filterFlags = 0;
+
+		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::FileName) != 0)
+			filterFlags |= IN_MOVE | IN_CREATE | IN_DELETE;
+
+		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::DirName) != 0)
+			filterFlags |= IN_MOVE | IN_CREATE | IN_DELETE;
+
+		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Attributes) != 0)
+			filterFlags |= IN_ATTRIB;
+
+		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Size) != 0)
+			filterFlags |= IN_ATTRIB | IN_MODIFY | IN_CREATE | IN_DELETE;
+
+		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::LastWrite) != 0)
+			filterFlags |= IN_ATTRIB | IN_MODIFY | IN_CLOSE_WRITE | IN_CREATE | IN_MOVED_TO;
+
+		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::LastAccess) != 0)
+			filterFlags |= IN_ATTRIB | IN_ACCESS;
+
+		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Creation) != 0)
+			filterFlags |= IN_CREATE;
+
+		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Security) != 0)
+			filterFlags |= IN_ATTRIB;
+
+		mPimpl->inHandle = inotify_init();
+
+		mPimpl->mFoldersToWatch.push_back(bs_new<FolderWatchInfo>(folderPath, subdirectories, filterFlags));
+		FolderWatchInfo* watchInfo = mPimpl->mFoldersToWatch.back();
+
+		if(mPimpl->mWorkerThread == nullptr)
+		{
+			mPimpl->mWorkerThread = bs_new<Thread>(std::bind(&FolderMonitor::workerThreadMain, this));
+
+			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)
+		{
+			watchInfo->startMonitor();
+		}
+		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();
+			bs_delete(watchInfo);
+
+			mPimpl->mFoldersToWatch.erase(findIter);
+		}
+
+		if(mPimpl->mFoldersToWatch.size() == 0)
+			stopMonitorAll();
+	}
+
+	void FolderMonitor::stopMonitorAll()
+	{
+		for(auto& watchInfo : mPimpl->mFoldersToWatch)
+		{
+			watchInfo->stopMonitor();
+
+			{
+				// 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)
+				Lock lock(mPimpl->mMainMutex);
+				bs_delete(watchInfo);
+			}
+		}
+
+		mPimpl->mFoldersToWatch.clear();
+
+		if(mPimpl->mWorkerThread != nullptr)
+		{
+			mPimpl->mWorkerThread->join();
+			bs_delete(mPimpl->mWorkerThread);
+			mPimpl->mWorkerThread = nullptr;
+		}
+
+		if(mPimpl->inHandle != 0)
+		{
+			close(mPimpl->inHandle);
+			mPimpl->inHandle = 0;
+		}
+	}
+
+	void FolderMonitor::workerThreadMain()
+	{
+	}
+
+	void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
+	{
+	}
+
+	void FolderMonitor::_update()
+	{
+		{
+			Lock lock(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);
+		}
+	}
+}

+ 2 - 2
Source/BansheeCore/Win32/BsWin32FolderMonitor.cpp

@@ -1,6 +1,6 @@
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
 //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-#include "Win32/BsWin32FolderMonitor.h"
+#include "Platform/BsFolderMonitor.h"
 #include "FileSystem/BsFileSystem.h"
 #include "FileSystem/BsFileSystem.h"
 #include "Error/BsException.h"
 #include "Error/BsException.h"
 
 
@@ -608,7 +608,7 @@ namespace bs
 					mActions.push_back(FileAction::createRenamed(watchInfo.mCachedOldFileName, fullPath));
 					mActions.push_back(FileAction::createRenamed(watchInfo.mCachedOldFileName, fullPath));
 				break;
 				break;
 			}
 			}
-    
+	
 		} while(notifyInfo.getNext());
 		} while(notifyInfo.getNext());
 
 
 		{
 		{

+ 0 - 82
Source/BansheeCore/Win32/BsWin32FolderMonitor.h

@@ -1,82 +0,0 @@
-//********************************** Banshee Engine (www.banshee3d.com) **************************************************//
-//**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
-#pragma once
-
-#include "BsCorePrerequisites.h"
-
-namespace bs
-{
-	/** @addtogroup Platform-Internal
-	 *  @{
-	 */
-
-	/** Types of notifications we would like to receive when we start a FolderMonitor on a certain folder. */
-	enum class FolderChange
-	{
-		FileName = 0x0001, /**< Called when filename changes. */
-		DirName = 0x0002, /**< Called when directory name changes. */
-		Attributes = 0x0004, /**< Called when attributes changes. */
-		Size = 0x0008, /**< Called when file size changes. */
-		LastWrite = 0x0010, /**< Called when file is written to. */
-		LastAccess = 0x0020, /**< Called when file is accessed. */
-		Creation = 0x0040, /**< Called when file is created. */
-		Security = 0x0080 /**< Called when file security descriptor changes. */
-	};
-
-	/**
-	 * Allows monitoring a file system folder for changes. Depending on the flags set this monitor can notify you when file
-	 * is changed/moved/renamed and similar.
-	 */
-	class BS_CORE_EXPORT FolderMonitor
-	{
-		struct Pimpl;
-		class FileNotifyInfo;
-		struct FolderWatchInfo;
-	public:
-		FolderMonitor();
-		~FolderMonitor();
-
-		/**
-		 * Starts monitoring a folder at the specified path.
-		 *
-		 * @param[in]	folderPath		Absolute path to the folder you want to monitor.
-		 * @param[in]	subdirectories	If true, provided folder and all of its subdirectories will be monitored for 
-		 *								changes. Otherwise only the provided folder will be monitored.
-		 * @param[in]	changeFilter	A set of flags you may OR together. Different notification events will trigger 
-		 *								depending on which flags you set.
-		 */
-		void startMonitor(const Path& folderPath, bool subdirectories, FolderChange changeFilter);
-
-		/** Stops monitoring the folder at the specified path. */
-		void stopMonitor(const Path& folderPath);
-
-		/**	Stops monitoring all folders that are currently being monitored. */
-		void stopMonitorAll();
-
-		/** Callbacks will only get fired after update is called. */
-		void _update();
-
-		/** Triggers when a file in the monitored folder is modified. Provides absolute path to the file. */
-		Event<void(const Path&)> onModified;
-
-		/**	Triggers when a file/folder is added in the monitored folder. Provides absolute path to the file/folder. */
-		Event<void(const Path&)> onAdded;
-
-		/**	Triggers when a file/folder is removed from the monitored folder. Provides absolute path to the file/folder. */
-		Event<void(const Path&)> onRemoved;
-
-		/**	Triggers when a file/folder is renamed in the monitored folder. Provides absolute path with old and new names. */
-		Event<void(const Path&, const Path&)> onRenamed;
-
-	private:
-		/**	Worker method that monitors the IO ports for any modification notifications. */
-		void workerThreadMain();
-
-		/**	Called by the worker thread whenever a modification notification is received. */
-		void handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo);
-
-		Pimpl* mPimpl;
-	};
-
-	/** @} */
-}