|
|
@@ -25,12 +25,18 @@
|
|
|
#include "FileSystem.h"
|
|
|
#include "FileWatcher.h"
|
|
|
#include "Log.h"
|
|
|
-
|
|
|
-#ifdef WIN32
|
|
|
-#include <windows.h>
|
|
|
-#endif
|
|
|
-
|
|
|
-namespace Urho3D
|
|
|
+
|
|
|
+#ifdef WIN32
|
|
|
+#include <windows.h>
|
|
|
+#elif __linux__
|
|
|
+#include <sys/inotify.h>
|
|
|
+extern "C" {
|
|
|
+// Need read/close for inotify
|
|
|
+#include "unistd.h"
|
|
|
+}
|
|
|
+#endif
|
|
|
+
|
|
|
+namespace Urho3D
|
|
|
{
|
|
|
|
|
|
static const unsigned BUFFERSIZE = 4096;
|
|
|
@@ -39,17 +45,27 @@ OBJECTTYPESTATIC(FileWatcher);
|
|
|
|
|
|
FileWatcher::FileWatcher(Context* context) :
|
|
|
Object(context),
|
|
|
- fileSystem_(GetSubsystem<FileSystem>()),
|
|
|
- watchSubDirs_(false)
|
|
|
-{
|
|
|
-}
|
|
|
-
|
|
|
-FileWatcher::~FileWatcher()
|
|
|
-{
|
|
|
- StopWatching();
|
|
|
-}
|
|
|
-
|
|
|
-bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
|
|
|
+ fileSystem_(GetSubsystem<FileSystem>()),
|
|
|
+ watchSubDirs_(false)
|
|
|
+{
|
|
|
+#if defined(ENABLE_FILEWATCHER)
|
|
|
+#if defined(__linux__)
|
|
|
+ watchHandle_ = inotify_init();
|
|
|
+#endif
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+FileWatcher::~FileWatcher()
|
|
|
+{
|
|
|
+ StopWatching();
|
|
|
+#if defined(ENABLE_FILEWATCHER)
|
|
|
+#if defined(__linux__)
|
|
|
+ close(watchHandle_);
|
|
|
+#endif
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
+bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
|
|
|
{
|
|
|
if (!fileSystem_)
|
|
|
{
|
|
|
@@ -57,13 +73,14 @@ bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
- // Stop any previous watching
|
|
|
- StopWatching();
|
|
|
-
|
|
|
-#if defined(WIN32) && defined(ENABLE_FILEWATCHER)
|
|
|
- String nativePath = GetNativePath(RemoveTrailingSlash(pathName));
|
|
|
-
|
|
|
- dirHandle_ = (void*)CreateFileW(
|
|
|
+ // Stop any previous watching
|
|
|
+ StopWatching();
|
|
|
+
|
|
|
+#if defined(ENABLE_FILEWATCHER)
|
|
|
+#if defined(WIN32)
|
|
|
+ String nativePath = GetNativePath(RemoveTrailingSlash(pathName));
|
|
|
+
|
|
|
+ dirHandle_ = (void*)CreateFileW(
|
|
|
WString(nativePath).CString(),
|
|
|
FILE_LIST_DIRECTORY,
|
|
|
FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
|
|
|
@@ -83,12 +100,61 @@ bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- LOGERROR("Failed to start watching path " + pathName);
|
|
|
- return false;
|
|
|
- }
|
|
|
-#else
|
|
|
- /// \todo Implement on Unix-like systems
|
|
|
- LOGERROR("FileWatcher not implemented, can not start watching path " + pathName);
|
|
|
+ LOGERROR("Failed to start watching path " + pathName);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+#elif defined(__linux__)
|
|
|
+ //String nativePath = GetNativePath(RemoveTrailingSlash(pathName));
|
|
|
+
|
|
|
+ int flags = IN_CREATE|IN_DELETE|IN_MODIFY|IN_MOVED_FROM|IN_MOVED_TO;
|
|
|
+ int handle;
|
|
|
+ dirHandle_;
|
|
|
+ handle = inotify_add_watch(watchHandle_, pathName.CString(), flags);
|
|
|
+
|
|
|
+ if (handle < 0)
|
|
|
+ {
|
|
|
+ LOGERROR("Failed to start watching path " + pathName);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Store the root path here when reconstructed with inotify later
|
|
|
+ dirHandle_[handle] = "";
|
|
|
+ path_ = AddTrailingSlash(pathName);
|
|
|
+ watchSubDirs_ = watchSubDirs;
|
|
|
+
|
|
|
+ if (watchSubDirs_)
|
|
|
+ {
|
|
|
+ Vector<String> subDirs;
|
|
|
+ fileSystem_->ScanDir(subDirs, pathName, "*", SCAN_DIRS, true);
|
|
|
+
|
|
|
+ for (unsigned i = 0; i < subDirs.Size(); ++i)
|
|
|
+ {
|
|
|
+ String subDirFullPath = AddTrailingSlash(path_ + subDirs[i]);
|
|
|
+
|
|
|
+ // Don't watch ./ or ../ sub-directories
|
|
|
+ if (!subDirFullPath.EndsWith("./"))
|
|
|
+ {
|
|
|
+ handle = inotify_add_watch(watchHandle_, subDirFullPath.CString(), flags);
|
|
|
+ if (handle < 0)
|
|
|
+ LOGERROR("Failed to start watching subdirectory path " + subDirFullPath);
|
|
|
+ else
|
|
|
+ {
|
|
|
+ // Store sub-directory to reconstruct later from inotify
|
|
|
+ dirHandle_[handle] = AddTrailingSlash(subDirs[i]);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ Start();
|
|
|
+
|
|
|
+ LOGDEBUG("Started watching path " + pathName);
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+#endif
|
|
|
+#else
|
|
|
+ /// \todo Implement on Unix-like systems
|
|
|
+ LOGERROR("FileWatcher not implemented, can not start watching path " + pathName);
|
|
|
return false;
|
|
|
#endif
|
|
|
}
|
|
|
@@ -105,24 +171,29 @@ void FileWatcher::StopWatching()
|
|
|
file.Close();
|
|
|
if (fileSystem_)
|
|
|
fileSystem_->Delete(dummyFileName);
|
|
|
-
|
|
|
- Stop();
|
|
|
-
|
|
|
- #ifdef WIN32
|
|
|
- CloseHandle((HANDLE)dirHandle_);
|
|
|
- #endif
|
|
|
-
|
|
|
- LOGDEBUG("Stopped watching path " + path_);
|
|
|
+
|
|
|
+ Stop();
|
|
|
+
|
|
|
+ #if defined(WIN32)
|
|
|
+ CloseHandle((HANDLE)dirHandle_);
|
|
|
+ #elif defined(__linux__)
|
|
|
+ for (HashMap<int, String>::Iterator i = dirHandle_.Begin(); i != dirHandle_.End(); ++i)
|
|
|
+ inotify_rm_watch(watchHandle_, i->first_);
|
|
|
+ dirHandle_.Clear();
|
|
|
+ #endif
|
|
|
+
|
|
|
+ LOGDEBUG("Stopped watching path " + path_);
|
|
|
path_.Clear();
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
-void FileWatcher::ThreadFunction()
|
|
|
-{
|
|
|
-#if defined(WIN32) && defined(ENABLE_FILEWATCHER)
|
|
|
- unsigned char buffer[BUFFERSIZE];
|
|
|
- DWORD bytesFilled = 0;
|
|
|
-
|
|
|
+
|
|
|
+void FileWatcher::ThreadFunction()
|
|
|
+{
|
|
|
+#if defined(ENABLE_FILEWATCHER)
|
|
|
+#if defined(WIN32)
|
|
|
+ unsigned char buffer[BUFFERSIZE];
|
|
|
+ DWORD bytesFilled = 0;
|
|
|
+
|
|
|
while (shouldRun_)
|
|
|
{
|
|
|
if (ReadDirectoryChangesW((HANDLE)dirHandle_,
|
|
|
@@ -163,12 +234,47 @@ void FileWatcher::ThreadFunction()
|
|
|
break;
|
|
|
else
|
|
|
offset += record->NextEntryOffset;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-#endif
|
|
|
-}
|
|
|
-
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+#elif defined(__linux__)
|
|
|
+ unsigned char buffer[BUFFERSIZE];
|
|
|
+
|
|
|
+ while (shouldRun_)
|
|
|
+ {
|
|
|
+ int i = 0;
|
|
|
+ int length = read(watchHandle_, buffer, sizeof(buffer));
|
|
|
+
|
|
|
+ if (length < 0)
|
|
|
+ return;
|
|
|
+
|
|
|
+ while (i < length)
|
|
|
+ {
|
|
|
+ inotify_event* event = (inotify_event*)&buffer[i];
|
|
|
+
|
|
|
+ if (event->len > 0)
|
|
|
+ {
|
|
|
+ if (event->mask & IN_MODIFY || event->mask & IN_MOVE)
|
|
|
+ {
|
|
|
+ String fileName;
|
|
|
+ fileName = dirHandle_[event->wd] + event->name;
|
|
|
+
|
|
|
+ {
|
|
|
+ MutexLock lock(changesMutex_);
|
|
|
+ // If we have 2 unprocessed modifies in a row into the same file, only record the first
|
|
|
+ if (changes_.Empty() || changes_.Back() != fileName)
|
|
|
+ changes_.Push(fileName);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ i += sizeof(inotify_event) + event->len;
|
|
|
+ }
|
|
|
+ }
|
|
|
+#endif
|
|
|
+#endif
|
|
|
+}
|
|
|
+
|
|
|
bool FileWatcher::GetNextChange(String& dest)
|
|
|
{
|
|
|
MutexLock lock(changesMutex_);
|