FileWatcher.cpp 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. // Copyright (c) 2008-2022 the Urho3D project
  2. // License: MIT
  3. #include "../Precompiled.h"
  4. #include "../IO/File.h"
  5. #include "../IO/FileSystem.h"
  6. #include "../IO/FileWatcher.h"
  7. #include "../IO/Log.h"
  8. #include "../Core/Profiler.h"
  9. #ifdef _WIN32
  10. #include "../Engine/WinWrapped.h"
  11. #elif __linux__
  12. #include <sys/inotify.h>
  13. extern "C"
  14. {
  15. // Need read/close for inotify
  16. #include "unistd.h"
  17. }
  18. #elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
  19. extern "C"
  20. {
  21. #include "../IO/MacFileWatcher.h"
  22. }
  23. #endif
  24. namespace Urho3D
  25. {
  26. #ifndef __APPLE__
  27. static const unsigned BUFFERSIZE = 4096;
  28. #endif
  29. FileWatcher::FileWatcher(Context* context) :
  30. Object(context),
  31. fileSystem_(GetSubsystem<FileSystem>()),
  32. delay_(1.0f),
  33. watchSubDirs_(false)
  34. {
  35. #ifdef URHO3D_FILEWATCHER
  36. #ifdef __linux__
  37. watchHandle_ = inotify_init();
  38. #elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
  39. supported_ = IsFileWatcherSupported();
  40. #endif
  41. #endif
  42. }
  43. FileWatcher::~FileWatcher()
  44. {
  45. StopWatching();
  46. #ifdef URHO3D_FILEWATCHER
  47. #ifdef __linux__
  48. close(watchHandle_);
  49. #endif
  50. #endif
  51. }
  52. bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
  53. {
  54. if (!fileSystem_)
  55. {
  56. URHO3D_LOGERROR("No FileSystem, can not start watching");
  57. return false;
  58. }
  59. // Stop any previous watching
  60. StopWatching();
  61. #if defined(URHO3D_FILEWATCHER) && defined(URHO3D_THREADING)
  62. #ifdef _WIN32
  63. String nativePath = GetNativePath(RemoveTrailingSlash(pathName));
  64. dirHandle_ = (void*)CreateFileW(
  65. WString(nativePath).CString(),
  66. FILE_LIST_DIRECTORY,
  67. FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
  68. nullptr,
  69. OPEN_EXISTING,
  70. FILE_FLAG_BACKUP_SEMANTICS,
  71. nullptr);
  72. if (dirHandle_ != INVALID_HANDLE_VALUE)
  73. {
  74. path_ = AddTrailingSlash(pathName);
  75. watchSubDirs_ = watchSubDirs;
  76. Run();
  77. URHO3D_LOGDEBUG("Started watching path " + pathName);
  78. return true;
  79. }
  80. else
  81. {
  82. URHO3D_LOGERROR("Failed to start watching path " + pathName);
  83. return false;
  84. }
  85. #elif defined(__linux__)
  86. int flags = IN_CREATE | IN_DELETE | IN_MODIFY | IN_MOVED_FROM | IN_MOVED_TO;
  87. int handle = inotify_add_watch(watchHandle_, pathName.CString(), (unsigned)flags);
  88. if (handle < 0)
  89. {
  90. URHO3D_LOGERROR("Failed to start watching path " + pathName);
  91. return false;
  92. }
  93. else
  94. {
  95. // Store the root path here when reconstructed with inotify later
  96. dirHandle_[handle] = "";
  97. path_ = AddTrailingSlash(pathName);
  98. watchSubDirs_ = watchSubDirs;
  99. if (watchSubDirs_)
  100. {
  101. Vector<String> subDirs;
  102. fileSystem_->ScanDir(subDirs, pathName, "*", SCAN_DIRS, true);
  103. for (unsigned i = 0; i < subDirs.Size(); ++i)
  104. {
  105. String subDirFullPath = AddTrailingSlash(path_ + subDirs[i]);
  106. // Don't watch ./ or ../ sub-directories
  107. if (!subDirFullPath.EndsWith("./"))
  108. {
  109. handle = inotify_add_watch(watchHandle_, subDirFullPath.CString(), (unsigned)flags);
  110. if (handle < 0)
  111. URHO3D_LOGERROR("Failed to start watching subdirectory path " + subDirFullPath);
  112. else
  113. {
  114. // Store sub-directory to reconstruct later from inotify
  115. dirHandle_[handle] = AddTrailingSlash(subDirs[i]);
  116. }
  117. }
  118. }
  119. }
  120. Run();
  121. URHO3D_LOGDEBUG("Started watching path " + pathName);
  122. return true;
  123. }
  124. #elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
  125. if (!supported_)
  126. {
  127. URHO3D_LOGERROR("Individual file watching not supported by this OS version, can not start watching path " + pathName);
  128. return false;
  129. }
  130. watcher_ = CreateFileWatcher(pathName.CString(), watchSubDirs);
  131. if (watcher_)
  132. {
  133. path_ = AddTrailingSlash(pathName);
  134. watchSubDirs_ = watchSubDirs;
  135. Run();
  136. URHO3D_LOGDEBUG("Started watching path " + pathName);
  137. return true;
  138. }
  139. else
  140. {
  141. URHO3D_LOGERROR("Failed to start watching path " + pathName);
  142. return false;
  143. }
  144. #else
  145. URHO3D_LOGERROR("FileWatcher not implemented, can not start watching path " + pathName);
  146. return false;
  147. #endif
  148. #else
  149. URHO3D_LOGDEBUG("FileWatcher feature not enabled");
  150. return false;
  151. #endif
  152. }
  153. void FileWatcher::StopWatching()
  154. {
  155. if (handle_)
  156. {
  157. shouldRun_ = false;
  158. // Create and delete a dummy file to make sure the watcher loop terminates
  159. // This is only required on Windows platform
  160. // TODO: Remove this temp write approach as it depends on user write privilege
  161. #ifdef _WIN32
  162. String dummyFileName = path_ + "dummy.tmp";
  163. File file(context_, dummyFileName, FILE_WRITE);
  164. file.Close();
  165. if (fileSystem_)
  166. fileSystem_->Delete(dummyFileName);
  167. #endif
  168. #if defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
  169. // Our implementation of file watcher requires the thread to be stopped first before closing the watcher
  170. Stop();
  171. #endif
  172. #ifdef _WIN32
  173. CloseHandle((HANDLE)dirHandle_);
  174. #elif defined(__linux__)
  175. for (HashMap<int, String>::Iterator i = dirHandle_.Begin(); i != dirHandle_.End(); ++i)
  176. inotify_rm_watch(watchHandle_, i->first_);
  177. dirHandle_.Clear();
  178. #elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
  179. CloseFileWatcher(watcher_);
  180. #endif
  181. #ifndef __APPLE__
  182. Stop();
  183. #endif
  184. URHO3D_LOGDEBUG("Stopped watching path " + path_);
  185. path_.Clear();
  186. }
  187. }
  188. void FileWatcher::SetDelay(float interval)
  189. {
  190. delay_ = Max(interval, 0.0f);
  191. }
  192. void FileWatcher::ThreadFunction()
  193. {
  194. #ifdef URHO3D_FILEWATCHER
  195. URHO3D_PROFILE_THREAD("FileWatcher Thread");
  196. #ifdef _WIN32
  197. unsigned char buffer[BUFFERSIZE];
  198. DWORD bytesFilled = 0;
  199. while (shouldRun_)
  200. {
  201. if (ReadDirectoryChangesW((HANDLE)dirHandle_,
  202. buffer,
  203. BUFFERSIZE,
  204. watchSubDirs_,
  205. FILE_NOTIFY_CHANGE_FILE_NAME |
  206. FILE_NOTIFY_CHANGE_LAST_WRITE,
  207. &bytesFilled,
  208. nullptr,
  209. nullptr))
  210. {
  211. unsigned offset = 0;
  212. while (offset < bytesFilled)
  213. {
  214. FILE_NOTIFY_INFORMATION* record = (FILE_NOTIFY_INFORMATION*)&buffer[offset];
  215. if (record->Action == FILE_ACTION_MODIFIED || record->Action == FILE_ACTION_RENAMED_NEW_NAME)
  216. {
  217. String fileName;
  218. const wchar_t* src = record->FileName;
  219. const wchar_t* end = src + record->FileNameLength / 2;
  220. while (src < end)
  221. fileName.AppendUTF8(String::DecodeUTF16(src));
  222. fileName = GetInternalPath(fileName);
  223. AddChange(fileName);
  224. }
  225. if (!record->NextEntryOffset)
  226. break;
  227. else
  228. offset += record->NextEntryOffset;
  229. }
  230. }
  231. }
  232. #elif defined(__linux__)
  233. unsigned char buffer[BUFFERSIZE];
  234. while (shouldRun_)
  235. {
  236. int i = 0;
  237. auto length = (int)read(watchHandle_, buffer, sizeof(buffer));
  238. if (length < 0)
  239. return;
  240. while (i < length)
  241. {
  242. auto* event = (inotify_event*)&buffer[i];
  243. if (event->len > 0)
  244. {
  245. if (event->mask & IN_MODIFY || event->mask & IN_MOVE)
  246. {
  247. String fileName;
  248. fileName = dirHandle_[event->wd] + event->name;
  249. AddChange(fileName);
  250. }
  251. }
  252. i += sizeof(inotify_event) + event->len;
  253. }
  254. }
  255. #elif defined(__APPLE__) && !defined(IOS) && !defined(TVOS)
  256. while (shouldRun_)
  257. {
  258. Time::Sleep(100);
  259. String changes = ReadFileWatcher(watcher_);
  260. if (!changes.Empty())
  261. {
  262. Vector<String> fileNames = changes.Split(1);
  263. for (unsigned i = 0; i < fileNames.Size(); ++i)
  264. AddChange(fileNames[i]);
  265. }
  266. }
  267. #endif
  268. #endif
  269. }
  270. void FileWatcher::AddChange(const String& fileName)
  271. {
  272. MutexLock lock(changesMutex_);
  273. // Reset the timer associated with the filename. Will be notified once timer exceeds the delay
  274. changes_[fileName].Reset();
  275. }
  276. bool FileWatcher::GetNextChange(String& dest)
  277. {
  278. MutexLock lock(changesMutex_);
  279. auto delayMsec = (unsigned)(delay_ * 1000.0f);
  280. if (changes_.Empty())
  281. return false;
  282. else
  283. {
  284. for (HashMap<String, Timer>::Iterator i = changes_.Begin(); i != changes_.End(); ++i)
  285. {
  286. if (i->second_.GetMSec(false) >= delayMsec)
  287. {
  288. dest = i->first_;
  289. changes_.Erase(i);
  290. return true;
  291. }
  292. }
  293. return false;
  294. }
  295. }
  296. }