Daniele Bartolini před 9 roky
rodič
revize
8fd1e4469a

+ 50 - 0
src/core/filesystem/file_monitor.h

@@ -0,0 +1,50 @@
+/*
+ * Copyright (c) 2012-2017 Daniele Bartolini and individual contributors.
+ * License: https://github.com/taylor001/crown/blob/master/LICENSE
+ */
+
+#pragma once
+
+#include "memory_types.h"
+#include "types.h"
+
+namespace crown
+{
+/// Enumerates file monitor events.
+///
+/// @ingroup Filesystem
+struct FileMonitorEvent
+{
+	enum Enum
+	{
+		CREATED,
+		DELETED,
+		RENAMED,
+		CHANGED,
+
+		COUNT
+	};
+};
+
+typedef void (*FileMonitorFunction)(void* user_data, FileMonitorEvent::Enum fme, bool is_dir, const char* path, const char* path_modified);
+
+struct FileMonitorImpl;
+
+/// Watches a directory for changes.
+///
+/// @ingroup Filesystem
+struct FileMonitor
+{
+	FileMonitorImpl* _impl;
+
+	FileMonitor(Allocator& a);
+	~FileMonitor();
+
+	// Starts monitoring the @a path.
+	void start(const char* path, bool recursive, FileMonitorFunction fmf, void* user_data);
+
+	// Stops monitoring.
+	void stop();
+};
+
+} // namespace crown

+ 353 - 0
src/core/filesystem/file_monitor_linux.cpp

@@ -0,0 +1,353 @@
+/*
+ * Copyright (c) 2012-2017 Daniele Bartolini and individual contributors.
+ * License: https://github.com/taylor001/crown/blob/master/LICENSE
+ */
+
+#include "platform.h"
+
+#if CROWN_PLATFORM_LINUX
+
+#include "dynamic_string.h"
+#include "file_monitor.h"
+#include "hash_map.h"
+#include "path.h"
+#include "temp_allocator.h"
+#include "thread.h"
+#include <dirent.h> // opendir, readdir
+#include <errno.h>
+#include <limits.h> // NAME_MAX
+#include <sys/inotify.h>
+#include <unistd.h> // read
+
+namespace crown
+{
+template <>
+struct hash<DynamicString>
+{
+	u32 operator()(const DynamicString& str) const
+	{
+		return str.to_string_id()._id;
+	}
+};
+
+struct FileMonitorImpl
+{
+	Allocator* _allocator;
+	int _fd;
+	HashMap<int, DynamicString> _watches;
+	HashMap<DynamicString, int> _watches_reverse;
+	Thread _thread;
+	bool _exit;
+	bool _recursive;
+	FileMonitorFunction _function;
+	void* _user_data;
+
+	FileMonitorImpl(Allocator& a)
+		: _allocator(&a)
+		, _fd(0)
+		, _watches(a)
+		, _watches_reverse(a)
+		, _exit(false)
+		, _recursive(false)
+		, _function(NULL)
+		, _user_data(NULL)
+	{
+	}
+
+	void add_watch(const char* path, bool recursive)
+	{
+		CE_ENSURE(path != NULL);
+		CE_ASSERT(path[strlen32(path)-1] != PATH_SEPARATOR, "Malformed path");
+
+		int wd = inotify_add_watch(_fd
+			, path
+			, IN_CREATE
+			| IN_DELETE      // File/directory deleted from watched directory.
+			| IN_MODIFY
+			| IN_ATTRIB
+			| IN_MOVE
+			| IN_DELETE_SELF // Watched file/directory was itself deleted.
+			| IN_DONT_FOLLOW // Don't dereference pathname if it is a symbolic link.
+			| IN_ONLYDIR     // Only watch pathname if it is a directory.
+			);
+		CE_ASSERT(wd != -1, "inotify_add_watch: errno: %d", errno);
+
+		TempAllocator512 ta;
+		DynamicString str(ta);
+		str = path;
+		hash_map::set(_watches, wd, str);
+		hash_map::set(_watches_reverse, str, wd);
+
+		// Add all sub-dirs recursively
+		DIR *dir;
+		struct dirent *entry;
+
+		if (!(dir = opendir(path)))
+			return;
+
+		while ((entry = readdir(dir)))
+		{
+			const char* dname = entry->d_name;
+
+			if (!strcmp(dname, ".") || !strcmp(dname, ".."))
+				continue;
+
+			// FIXME: some filesystems do not support DT_DIR.
+			if (entry->d_type != DT_DIR)
+				continue;
+
+			TempAllocator512 ta;
+			DynamicString str(ta);
+			path::join(str, path, dname);
+
+			add_watch(str.c_str(), recursive);
+		}
+
+		closedir(dir);
+	}
+
+	void start(const char* path, bool recursive, FileMonitorFunction fmf, void* user_data)
+	{
+		CE_ENSURE(NULL != path);
+		CE_ENSURE(NULL != fmf);
+
+		_recursive = recursive;
+		_function = fmf;
+		_user_data = user_data;
+
+		_fd = inotify_init();
+		CE_ASSERT(_fd != -1, "inotify_init: errno: %d", errno);
+
+		add_watch(path, recursive);
+
+		_thread.start(run, this);
+	}
+
+	void stop()
+	{
+		_exit = true;
+		_thread.stop();
+
+		close(_fd);
+	}
+
+	int watch()
+	{
+		while (!_exit)
+		{
+			TempAllocator512 ta;
+
+			fd_set set;
+			struct timeval timeout;
+			u32 cookie = 0;
+			u32 cookie_mask = 0;
+			DynamicString cookie_path(ta);
+
+			FD_ZERO(&set);
+			FD_SET(_fd, &set);
+
+			timeout.tv_sec = 0;
+			timeout.tv_usec = 32*1000;
+
+			// select returns 0 if timeout, 1 if input available, -1 if error.
+			if (select(FD_SETSIZE, &set, NULL, NULL, &timeout) == 0)
+				continue;
+
+#define BUF_LEN sizeof(struct inotify_event) + NAME_MAX + 1
+			char buf[BUF_LEN*16];
+#undef BUF_LEN
+			ssize_t len = read(_fd, buf, sizeof(buf));
+			if (len == 0)
+				return 0;
+			if (len == -1)
+				return -1;
+
+			for (char* p = buf; p < buf + len;)
+			{
+				inotify_event* ev = (inotify_event*)p;
+
+				if (ev->mask & IN_IGNORED)
+				{
+					// Watch was removed explicitly (inotify_rm_watch(2)) or
+					// automatically (file was deleted, or filesystem was
+					// unmounted).
+					hash_map::remove(_watches, ev->wd);
+				}
+				if (ev->mask & IN_CREATE)
+				{
+					DynamicString path(ta);
+					full_path(path, ev->wd, ev->name);
+
+					_function(_user_data
+						, FileMonitorEvent::CREATED
+						, ev->mask & IN_ISDIR
+						, path.c_str()
+						, NULL
+						);
+
+					if (ev->mask & IN_ISDIR)
+						add_watch(path.c_str(), _recursive);
+				}
+				if (ev->mask & IN_DELETE)
+				{
+					DynamicString path(ta);
+					full_path(path, ev->wd, ev->name);
+
+					_function(_user_data
+						, FileMonitorEvent::DELETED
+						, ev->mask & IN_ISDIR
+						, path.c_str()
+						, NULL
+						);
+				}
+				if (ev->mask & IN_MODIFY || ev->mask & IN_ATTRIB)
+				{
+					DynamicString path(ta);
+					full_path(path, ev->wd, ev->name);
+
+					_function(_user_data
+						, FileMonitorEvent::CHANGED
+						, ev->mask & IN_ISDIR
+						, path.c_str()
+						, NULL
+						);
+				}
+				if (ev->mask & IN_MOVED_FROM)
+				{
+					// Two consecutive IN_MOVED_FROM
+					if (cookie != 0)
+					{
+						_function(_user_data
+							, FileMonitorEvent::DELETED
+							, cookie_mask & IN_ISDIR
+							, cookie_path.c_str()
+							, NULL
+							);
+
+						u32 wd = hash_map::get(_watches_reverse, cookie_path, INT32_MAX);
+						hash_map::remove(_watches_reverse, cookie_path);
+						inotify_rm_watch(_fd, wd);
+
+						cookie = 0;
+						cookie_mask = 0;
+					}
+					else
+					{
+						DynamicString path(ta);
+						full_path(path, ev->wd, ev->name);
+
+						cookie = ev->cookie;
+						cookie_mask = ev->mask;
+						cookie_path = path;
+					}
+				}
+				if (ev->mask & IN_MOVED_TO)
+				{
+					if (cookie == ev->cookie)
+					{
+						// File or directory has been renamed
+						DynamicString path(ta);
+						full_path(path, ev->wd, ev->name);
+
+						_function(_user_data
+							, FileMonitorEvent::RENAMED
+							, ev->mask & IN_ISDIR
+							, cookie_path.c_str()
+							, path.c_str()
+							);
+
+						cookie = 0;
+						cookie_mask = 0;
+
+						if (ev->mask & IN_ISDIR)
+						{
+							u32 wd = hash_map::get(_watches_reverse, cookie_path, INT32_MAX);
+							hash_map::remove(_watches_reverse, cookie_path);
+							inotify_rm_watch(_fd, wd);
+
+							add_watch(path.c_str(), _recursive);
+						}
+					}
+					else
+					{
+						// File or directory was moved to this folder
+						DynamicString path(ta);
+						full_path(path, ev->wd, ev->name);
+
+						_function(_user_data
+							, FileMonitorEvent::CREATED
+							, ev->mask & IN_ISDIR
+							, path.c_str()
+							, NULL
+							);
+
+						cookie = 0;
+						cookie_mask = 0;
+
+						if (ev->mask & IN_ISDIR)
+							add_watch(path.c_str(), _recursive);
+					}
+				}
+
+				p += sizeof(inotify_event) + ev->len;
+			}
+
+			// Unpaired IN_MOVE_TO
+			if (cookie != 0)
+			{
+				_function(_user_data
+					, FileMonitorEvent::DELETED
+					, cookie_mask & IN_ISDIR
+					, cookie_path.c_str()
+					, NULL
+					);
+
+				u32 wd = hash_map::get(_watches_reverse, cookie_path, INT32_MAX);
+				hash_map::remove(_watches_reverse, cookie_path);
+				inotify_rm_watch(_fd, wd);
+
+				cookie = 0;
+				cookie_mask = 0;
+			}
+		}
+
+		return 0;
+	}
+
+	void full_path(DynamicString& path, int wd, const char* name)
+	{
+		TempAllocator512 ta;
+		DynamicString path_base(ta);
+		path_base = hash_map::get(_watches, wd, path_base);
+		path::join(path, path_base.c_str(), name);
+	}
+
+	static int run(void* thiz)
+	{
+		return ((FileMonitorImpl*)thiz)->watch();
+	}
+};
+
+FileMonitor::FileMonitor(Allocator& a)
+{
+	_impl = CE_NEW(a, FileMonitorImpl)(a);
+}
+
+FileMonitor::~FileMonitor()
+{
+	CE_DELETE(*_impl->_allocator, _impl);
+}
+
+void FileMonitor::start(const char* path, bool recursive, FileMonitorFunction fmf, void* user_data)
+{
+	_impl->start(path, recursive, fmf, user_data);
+}
+
+void FileMonitor::stop()
+{
+	_impl->stop();
+}
+
+} // namespace crown
+
+#endif // CROWN_PLATFORM_LINUX

+ 34 - 0
src/core/filesystem/file_monitor_noop.cpp

@@ -0,0 +1,34 @@
+/*
+ * Copyright (c) 2012-2017 Daniele Bartolini and individual contributors.
+ * License: https://github.com/taylor001/crown/blob/master/LICENSE
+ */
+
+#include "platform.h"
+
+#if !CROWN_PLATFORM_LINUX
+
+#include "file_monitor.h"
+
+namespace crown
+{
+FileMonitor::FileMonitor(Allocator& /*a*/)
+	: _impl(NULL)
+{
+}
+
+FileMonitor::~FileMonitor()
+{
+}
+
+void FileMonitor::start(const char* /*path*/, bool /*recursive*/, FileMonitorFunction /*fmf*/, void* /*user_data*/)
+{
+}
+
+void FileMonitor::stop()
+{
+}
+
+} // namespace crown
+
+#endif // !CROWN_PLATFORM_LINUX
+

+ 1 - 0
src/core/filesystem/filesystem_types.h

@@ -11,6 +11,7 @@ namespace crown
 {
 class Filesystem;
 class File;
+struct FileMonitor;
 
 /// Enumerates file open modes.
 ///