Bläddra i källkod

WIP: Linux port
- Most of the work on folder monitor finished

Marko Pintera 8 år sedan
förälder
incheckning
adf3bd4a8e

+ 1 - 0
Source/BansheeCore/CMakeSources.cmake

@@ -606,6 +606,7 @@ set(BS_BANSHEECORE_SRC_PLATFORM_UNIX
 	"Linux/BsLinuxPlatform.cpp"
 	"Linux/BsLinuxWindow.cpp"
 	"Linux/BsLinuxDragAndDrop.cpp"
+	"Linux/BsLinuxFolderMonitor.cpp"
 )
 
 if(WIN32)

+ 523 - 0
Source/BansheeCore/Linux/BsLinuxFolderMonitor.cpp

@@ -0,0 +1,523 @@
+//********************************** 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 <sys/inotify.h>
+
+namespace bs
+{
+	struct FolderMonitor::FolderWatchInfo
+	{
+		FolderWatchInfo(const Path& folderToMonitor, int inHandle, bool monitorSubdirectories, FolderChangeBits filter);
+		~FolderWatchInfo();
+
+		void startMonitor();
+		void stopMonitor();
+
+		void addPath(const Path& path);
+		void removePath(const Path& path);
+		Path getPath(INT32 handle);
+
+		Path folderToMonitor;
+		int dirHandle;
+		bool monitorSubdirectories;
+		FolderChangeBits filter;
+
+		UnorderedMap<Path, INT32> pathToHandle;
+		UnorderedMap<INT32, Path> handleToPath;
+	};
+
+	FolderMonitor::FolderWatchInfo::FolderWatchInfo(const Path& folderToMonitor, int inHandle, bool monitorSubdirectories,
+													FolderChangeBits filter)
+		: folderToMonitor(folderToMonitor), dirHandle(0), monitorSubdirectories(monitorSubdirectories)
+		, filter(filter)
+	{ }
+
+	FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
+	{
+		stopMonitor();
+	}
+
+	void FolderMonitor::FolderWatchInfo::startMonitor()
+	{
+		addPath(folderToMonitor);
+
+		if(monitorSubdirectories)
+		{
+			FileSystem::iterate(folderToMonitor, nullptr, [&pathToHandle](const Path& path)
+			{
+				addPath(path);
+				return true;
+			});
+		}
+	}
+
+	void FolderMonitor::FolderWatchInfo::stopMonitor()
+	{
+		for(auto& entry : pathToHandle)
+			inotify_rm_watch(dirHandle, entry.second);
+
+		pathToHandle.clear();
+	}
+
+	void FolderMonitor::FolderWatchInfo::addPath(const Path& path)
+	{
+		String pathString = path.toString();
+
+		INT32 watchHandle = inotify_add_watch(dirHandle, pathString.c_str(), IN_ALL_EVENTS);
+		pathToHandle[path] = watchHandle;
+		handleToPath[watchHandle] = path;
+	}
+
+	void FolderMonitor::FolderWatchInfo::removePath(const Path& path)
+	{
+		auto iterFind = pathToHandle.find(path);
+		if(iterFind != pathToHandle.end())
+		{
+			INT32 watchHandle = iterFind->second;
+			pathToHandle.erase(iterFind);
+
+			handleToPath.erase(watchHandle);
+		}
+	}
+
+	Path FolderMonitor::FolderWatchInfo::getPath(INT32 handle)
+	{
+		auto iterFind = handleToPath.find(handle);
+		if(iterFind != handleToPath.end())
+			return iterFind->second;
+
+		return Path::BLANK;
+	}
+
+	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*> monitors;
+
+		Vector<FileAction*> fileActions;
+		Vector<FileAction*> activeFileActions;
+
+		int inHandle;
+		bool started;
+		Mutex mainMutex;
+		Thread* workerThread;
+	};
+
+	FolderMonitor::FolderMonitor()
+	{
+		m = bs_new<Pimpl>();
+		m->workerThread = nullptr;
+		m->inHandle = 0;
+		m->started = false;
+	}
+
+	FolderMonitor::~FolderMonitor()
+	{
+		stopMonitorAll();
+
+		// No need for mutex since we know worker thread is shut down by now
+		for(auto& action : m->fileActions)
+			FileAction::destroy(action);
+
+		bs_delete(m);
+	}
+
+	void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChangeBits changeFilter)
+	{
+		if(!FileSystem::isDirectory(folderPath))
+		{
+			LOGERR("Provided path \"" + folderPath.toString() + "\" is not a directory");
+			return;
+		}
+
+		// Check if there is overlap with existing monitors
+		for(auto& monitor : m->monitors)
+		{
+			// Identical monitor exists
+			if(monitor->folderToMonitor.equals(folderPath))
+			{
+				LOGWRN("Folder is already monitored, cannot monitor it again.");
+				return;
+			}
+
+			// This directory is part of a directory that's being monitored
+			if(monitor->monitorSubdirectories && folderPath.includes(monitor->folderToMonitor))
+			{
+				LOGWRN("Folder is already monitored, cannot monitor it again.");
+				return;
+			}
+
+			// This directory would include a directory of another monitor
+			if(subdirectories && monitor->folderToMonitor.includes(folderPath))
+			{
+				LOGWRN("Cannot add a recursive monitor as it conflicts with a previously monitored path");
+				return;
+			}
+		}
+
+		// Initialize inotify if required
+		if(!m->started)
+		{
+			Lock lock(m->mainMutex);
+
+			m->inHandle = inotify_init();
+			m->started = true;
+		}
+
+		FolderWatchInfo* watchInfo = bs_new<FolderWatchInfo>(folderPath, subdirectories, changeFilter);
+
+		// Register and start the monitor
+		{
+			Lock lock(m->mainMutex);
+
+			m->monitors.push_back(watchInfo);
+			watchInfo->startMonitor();
+		}
+
+		// Start the worker thread if it isn't already
+		if(m->workerThread == nullptr)
+		{
+			m->workerThread = bs_new<Thread>(std::bind(&FolderMonitor::workerThreadMain, this));
+
+			if(m->workerThread == nullptr)
+				LOGERR("Failed to create a new worker thread for folder monitoring");
+		}
+	}
+
+	void FolderMonitor::stopMonitor(const Path& folderPath)
+	{
+		auto findIter = std::find_if(m->monitors.begin(), m->monitors.end(),
+			[&](const FolderWatchInfo* x) { return x->folderToMonitor == folderPath; });
+
+		if(findIter != m->monitors.end())
+		{
+			Lock lock(m->mainMutex);
+			FolderWatchInfo* watchInfo = *findIter;
+
+			watchInfo->stopMonitor();
+			bs_delete(watchInfo);
+
+			m->monitors.erase(findIter);
+		}
+
+		if(m->monitors.size() == 0)
+			stopMonitorAll();
+	}
+
+	void FolderMonitor::stopMonitorAll()
+	{
+		{
+			Lock lock(m->mainMutex);
+			for (auto& watchInfo : m->monitors)
+			{
+				watchInfo->stopMonitor();
+				bs_delete(watchInfo);
+			}
+
+			m->monitors.clear();
+		}
+
+		{
+			Lock lock(m->mainMutex);
+			if (m->started)
+			{
+				// This should trigger worker thread shut-down
+				close(m->inHandle);
+				m->inHandle = 0;
+				m->started = false;
+			}
+		}
+
+		if(m->workerThread != nullptr)
+		{
+			m->workerThread->join();
+			bs_delete(m->workerThread);
+			m->workerThread = nullptr;
+		}
+	}
+
+	void FolderMonitor::workerThreadMain()
+	{
+		static const UINT32 BUFFER_SIZE = 16384;
+
+		INT32 watchHandle;
+		{
+			Lock(m->mainMutex);
+			watchHandle = m->inHandle;
+		}
+
+		UINT8 buffer[BUFFER_SIZE];
+
+		while(true)
+		{
+			INT32 length = (INT32)read(watchHandle, buffer, sizeof(buffer));
+
+			// Handle was closed, shutdown thread
+			if (length < 0)
+				return;
+
+			UINT32 readPos = 0;
+			while(readPos < length)
+			{
+				inotify_event* event = (inotify_event*)&buffer[readPos];
+				if(event->len > 0)
+				{
+					{
+						Lock lock(m->mainMutex);
+
+						Path path;
+						FolderWatchInfo* monitor = nullptr;
+						for (auto& entry : m->monitors)
+						{
+							path = entry->getPath(event->wd);
+							if (!path.isEmpty())
+							{
+								path.append(event->name);
+								monitor = entry;
+								break;
+							}
+						}
+
+						// This can happen if the path got removed during some recent previous event
+						if(monitor == nullptr)
+							goto next;
+
+						// Need to add/remove sub-directories to/from watch list
+						bool isDirectory = (event->mask & IN_ISDIR) != 0;
+						if(isDirectory && monitor->monitorSubdirectories)
+						{
+							bool added = (event->mask & (IN_CREATE | IN_MOVED_TO)) != 0;
+							bool removed = (event->mask & (IN_DELETE | IN_MOVED_FROM)) != 0;
+
+							if(added)
+								monitor->addPath(path);
+							else if(removed)
+								monitor->removePath(path);
+						}
+
+						// Actually trigger the events
+
+						// File/folder was added
+						if(((event->mask & IN_CREATE | IN_MOVED_TO) != 0))
+						{
+							if (isDirectory)
+							{
+								if (monitor->filter.isSet(FolderChangeBit::DirName))
+									m->fileActions.push_back(FileAction::createAdded(path.toWString()));
+							}
+							else
+							{
+								if (monitor->filter.isSet(FolderChangeBit::FileName))
+									m->fileActions.push_back(FileAction::createAdded(path.toWString()));
+							}
+						}
+
+						// File/folder was removed
+						if(((event->mask & (IN_DELETE | IN_MOVED_FROM)) != 0))
+						{
+							if(isDirectory)
+							{
+								if(monitor->filter.isSet(FolderChangeBit::DirName))
+									m->fileActions.push_back(FileAction::createRemoved(path.toWString()));
+							}
+							else
+							{
+								if(monitor->filter.isSet(FolderChangeBit::FileName))
+									m->fileActions.push_back(FileAction::createRemoved(path.toWString()));
+							}
+						}
+
+						// File was modified
+						if(((event->mask & IN_CLOSE_WRITE) != 0) && monitor->filter.isSet(FolderChangeBit::FileWrite))
+						{
+							m->fileActions.push_back(FileAction::createModified(path.toWString()));
+						}
+
+						// Note: Not reporting renames, instead a remove + add event is created. To support renames I'd need
+						// to defer all event triggering until I have processed move event pairs and determined if the
+						// move is a rename (i.e. parent folder didn't change). All events need to be deferred (not just
+						// move events) in order to preserve the event ordering. For now this is too much hassle considering
+						// no external code relies on the rename functionality.
+					}
+				}
+
+				next:
+				readPos += sizeof(inotify_event) + event->len;
+			}
+		}
+	}
+
+	void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
+	{
+		// Do nothing
+	}
+
+	void FolderMonitor::_update()
+	{
+		{
+			Lock lock(m->mainMutex);
+
+			std::swap(m->fileActions, m->activeFileActions);
+		}
+
+		for(auto& action : m->activeFileActions)
+		{
+			// 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;
+					continue;
+				}
+				else
+				{
+					if (action->lastSize != size)
+					{
+						action->lastSize = size;
+						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;
+			}
+
+			FileAction::destroy(action);
+		}
+
+		m->activeFileActions.clear();
+	}
+}

+ 9 - 11
Source/BansheeCore/Platform/BsFolderMonitor.h

@@ -11,18 +11,16 @@ namespace bs
 	 */
 
 	/** Types of notifications we would like to receive when we start a FolderMonitor on a certain folder. */
-	enum class FolderChange
+	enum class FolderChangeBit
 	{
-		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. */
+		FileName 	= 1 << 0, /**< Called when file is created, moved or removed. */
+		DirName 	= 1 << 1, /**< Called when directory is created, moved or removed. */
+		FileWrite 	= 1 << 2, /**< Called when file is written to. */
 	};
 
+	typedef Flags<FolderChangeBit> FolderChangeBits;
+	BS_FLAGS_OPERATORS(FolderChangeBit)
+
 	/**
 	 * 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.
@@ -45,7 +43,7 @@ namespace bs
 		 * @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);
+		void startMonitor(const Path& folderPath, bool subdirectories, FolderChangeBits changeFilter);
 
 		/** Stops monitoring the folder at the specified path. */
 		void stopMonitor(const Path& folderPath);
@@ -75,7 +73,7 @@ namespace bs
 		/**	Called by the worker thread whenever a modification notification is received. */
 		void handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo);
 
-		Pimpl* mPimpl;
+		Pimpl* m;
 	};
 
 	/** @} */

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

@@ -1,386 +0,0 @@
-//********************************** 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);
-		}
-	}
-}

+ 5 - 20
Source/BansheeCore/Win32/BsWin32FolderMonitor.cpp

@@ -328,7 +328,7 @@ namespace bs
 		bs_delete(mPimpl);
 	}
 
-	void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChange changeFilter)
+	void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChangeBits changeFilter)
 	{
 		if(!FileSystem::isDirectory(folderPath))
 		{
@@ -348,30 +348,15 @@ namespace bs
 
 		DWORD filterFlags = 0;
 
-		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::FileName) != 0)
+		if(changeFilter.isSet(FolderChangeBit::FileName))
 			filterFlags |= FILE_NOTIFY_CHANGE_FILE_NAME;
 
-		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::DirName) != 0)
+		if(changeFilter.isSet(FolderChangeBit::DirName))
 			filterFlags |= FILE_NOTIFY_CHANGE_DIR_NAME;
 
-		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Attributes) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
-
-		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Size) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_SIZE;
-
-		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::LastWrite) != 0)
+		if(changeFilter.isSet(FolderChangeBit::LastWrite))
 			filterFlags |= FILE_NOTIFY_CHANGE_LAST_WRITE;
 
-		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::LastAccess) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
-
-		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Creation) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_CREATION;
-
-		if((((UINT32)changeFilter) & (UINT32)bs::FolderChange::Security) != 0)
-			filterFlags |= FILE_NOTIFY_CHANGE_SECURITY;
-
 		mPimpl->mFoldersToWatch.push_back(bs_new<FolderWatchInfo>(folderPath, dirHandle, subdirectories, filterFlags));
 		FolderWatchInfo* watchInfo = mPimpl->mFoldersToWatch.back();
 
@@ -381,7 +366,7 @@ namespace bs
 		{
 			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()));
+			BS_EXCEPT(InternalErrorException, "Failed to open completion port for folder monitoring. Error code: " + toString((UINT64)GetLastError()));
 		}
 
 		if(mPimpl->mWorkerThread == nullptr)

+ 59 - 5
Source/BansheeUtility/Linux/BsUnixFileSystem.cpp

@@ -122,7 +122,7 @@ namespace bs
 
 		DataStream::AccessMode accessMode = DataStream::READ;
 		if (!readOnly)
-			accessMode = (DataStream::AccessMode)(accessMode | (UINT32)DataStream::WRITE);
+			accessMode = (accessMode | (UINT32)DataStream::WRITE);
 
 		return bs_shared_ptr_new<FileDataStream>(path, accessMode, true);
 	}
@@ -138,12 +138,12 @@ namespace bs
 
 		if (stat(path.toString().c_str(), &st_buf) == 0)
 		{
-			return st_buf.st_size;
+			return (UINT64)st_buf.st_size;
 		}
 		else
 		{
 			HANDLE_PATH_ERROR(path.toString(), errno);
-			return -1;
+			return (UINT64)-1;
 		}
 	}
 
@@ -186,7 +186,6 @@ namespace bs
 		if (unix_isFile(pathStr))
 			return;
 
-
 		DIR *dp = opendir(pathStr.c_str());
 		if (dp == NULL)
 		{
@@ -238,7 +237,62 @@ namespace bs
 	bool FileSystem::iterate(const Path& dirPath, std::function<bool(const Path&)> fileCallback,
 		std::function<bool(const Path&)> dirCallback, bool recursive)
 	{
-		BS_ASSERT(!"TODOPORT: implement FileSystem::iterate()");
+		String pathStr = dirPath.toString();
+
+		if (unix_isFile(pathStr))
+			return false;
+
+		DIR* dirHandle = opendir(pathStr.c_str());
+		if (dirHandle == nullptr)
+		{
+			HANDLE_PATH_ERROR(pathStr, errno);
+			return false;
+		}
+
+		dirent* entry;
+		while((entry = readdir(dirHandle)))
+		{
+			String filename(entry->d_name);
+			if (filename == "." || filename == "..")
+				continue;
+
+			Path fullPath = dirPath;
+			if (unix_isDirectory(pathStr + "/" + filename))
+			{
+				Path childDir = fullPath.append(filename + "/");
+				if (dirCallback != nullptr)
+				{
+					if (!dirCallback(childDir))
+					{
+						closedir(dirHandle);
+						return false;
+					}
+				}
+
+				if (recursive)
+				{
+					if (!iterate(childDir, fileCallback, dirCallback, recursive))
+					{
+						closedir(dirHandle);
+						return false;
+					}
+				}
+			}
+			else
+			{
+				Path filePath = fullPath.append(filename);
+				if (fileCallback != nullptr)
+				{
+					if (!fileCallback(filePath))
+					{
+						closedir(dirHandle);
+						return false;
+					}
+				}
+			}
+		}
+		closedir(dirHandle);
+
 		return true;
 	}
 

+ 4 - 2
Source/SBansheeEditor/Wrappers/BsScriptFolderMonitor.cpp

@@ -54,8 +54,10 @@ namespace bs
 		{
 			Path folderPath = MonoUtil::monoToWString(folder);
 
-			FolderChange folderChanges = (FolderChange)((UINT32)FolderChange::FileName | (UINT32)FolderChange::DirName |
-				(UINT32)FolderChange::Creation | (UINT32)FolderChange::LastWrite);
+			FolderChangeBits folderChanges;
+			folderChanges |= FolderChangeBit::FileName;
+			folderChanges |= FolderChangeBit::DirName;
+			folderChanges |= FolderChangeBit::FileWrite;
 
 			monitor = bs_new<FolderMonitor>();
 			monitor->startMonitor(folderPath, true, folderChanges);