FileWatcher.cpp 9.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. //
  2. // Copyright (c) 2008-2015 the Urho3D project.
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to deal
  6. // in the Software without restriction, including without limitation the rights
  7. // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
  8. // copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
  19. // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
  20. // THE SOFTWARE.
  21. //
  22. #include "../IO/File.h"
  23. #include "../IO/FileSystem.h"
  24. #include "../IO/FileWatcher.h"
  25. #include "../IO/Log.h"
  26. #include "../Core/Timer.h"
  27. #ifdef WIN32
  28. #include <windows.h>
  29. #elif __linux__
  30. #include <sys/inotify.h>
  31. extern "C" {
  32. // Need read/close for inotify
  33. #include "unistd.h"
  34. }
  35. #elif defined(__APPLE__) && !defined(IOS)
  36. extern "C" {
  37. #include "../IO/MacFileWatcher.h"
  38. }
  39. #endif
  40. namespace Urho3D
  41. {
  42. #ifndef __APPLE__
  43. static const unsigned BUFFERSIZE = 4096;
  44. #endif
  45. FileWatcher::FileWatcher(Context* context) :
  46. Object(context),
  47. fileSystem_(GetSubsystem<FileSystem>()),
  48. delay_(1.0f),
  49. watchSubDirs_(false)
  50. {
  51. #if defined(URHO3D_FILEWATCHER)
  52. #if defined(__linux__)
  53. watchHandle_ = inotify_init();
  54. #elif defined(__APPLE__) && !defined(IOS)
  55. supported_ = IsFileWatcherSupported();
  56. #endif
  57. #endif
  58. }
  59. FileWatcher::~FileWatcher()
  60. {
  61. StopWatching();
  62. #if defined(URHO3D_FILEWATCHER)
  63. #if defined(__linux__)
  64. close(watchHandle_);
  65. #endif
  66. #endif
  67. }
  68. bool FileWatcher::StartWatching(const String& pathName, bool watchSubDirs)
  69. {
  70. if (!fileSystem_)
  71. {
  72. LOGERROR("No FileSystem, can not start watching");
  73. return false;
  74. }
  75. // Stop any previous watching
  76. StopWatching();
  77. #if defined(URHO3D_FILEWATCHER)
  78. #if defined(WIN32)
  79. String nativePath = GetNativePath(RemoveTrailingSlash(pathName));
  80. dirHandle_ = (void*)CreateFileW(
  81. WString(nativePath).CString(),
  82. FILE_LIST_DIRECTORY,
  83. FILE_SHARE_WRITE | FILE_SHARE_READ | FILE_SHARE_DELETE,
  84. 0,
  85. OPEN_EXISTING,
  86. FILE_FLAG_BACKUP_SEMANTICS,
  87. 0);
  88. if (dirHandle_ != INVALID_HANDLE_VALUE)
  89. {
  90. path_ = AddTrailingSlash(pathName);
  91. watchSubDirs_ = watchSubDirs;
  92. Run();
  93. LOGDEBUG("Started watching path " + pathName);
  94. return true;
  95. }
  96. else
  97. {
  98. LOGERROR("Failed to start watching path " + pathName);
  99. return false;
  100. }
  101. #elif defined(__linux__)
  102. int flags = IN_CREATE|IN_DELETE|IN_MODIFY|IN_MOVED_FROM|IN_MOVED_TO;
  103. int handle = inotify_add_watch(watchHandle_, pathName.CString(), flags);
  104. if (handle < 0)
  105. {
  106. LOGERROR("Failed to start watching path " + pathName);
  107. return false;
  108. }
  109. else
  110. {
  111. // Store the root path here when reconstructed with inotify later
  112. dirHandle_[handle] = "";
  113. path_ = AddTrailingSlash(pathName);
  114. watchSubDirs_ = watchSubDirs;
  115. if (watchSubDirs_)
  116. {
  117. Vector<String> subDirs;
  118. fileSystem_->ScanDir(subDirs, pathName, "*", SCAN_DIRS, true);
  119. for (unsigned i = 0; i < subDirs.Size(); ++i)
  120. {
  121. String subDirFullPath = AddTrailingSlash(path_ + subDirs[i]);
  122. // Don't watch ./ or ../ sub-directories
  123. if (!subDirFullPath.EndsWith("./"))
  124. {
  125. handle = inotify_add_watch(watchHandle_, subDirFullPath.CString(), flags);
  126. if (handle < 0)
  127. LOGERROR("Failed to start watching subdirectory path " + subDirFullPath);
  128. else
  129. {
  130. // Store sub-directory to reconstruct later from inotify
  131. dirHandle_[handle] = AddTrailingSlash(subDirs[i]);
  132. }
  133. }
  134. }
  135. }
  136. Run();
  137. LOGDEBUG("Started watching path " + pathName);
  138. return true;
  139. }
  140. #elif defined(__APPLE__) && !defined(IOS)
  141. if (!supported_)
  142. {
  143. LOGERROR("Individual file watching not supported by this OS version, can not start watching path " + pathName);
  144. return false;
  145. }
  146. watcher_ = CreateFileWatcher(pathName.CString(), watchSubDirs);
  147. if (watcher_)
  148. {
  149. path_ = AddTrailingSlash(pathName);
  150. watchSubDirs_ = watchSubDirs;
  151. Run();
  152. LOGDEBUG("Started watching path " + pathName);
  153. return true;
  154. }
  155. else
  156. {
  157. LOGERROR("Failed to start watching path " + pathName);
  158. return false;
  159. }
  160. #else
  161. LOGERROR("FileWatcher not implemented, can not start watching path " + pathName);
  162. return false;
  163. #endif
  164. #else
  165. LOGDEBUG("FileWatcher feature not enabled");
  166. return false;
  167. #endif
  168. }
  169. void FileWatcher::StopWatching()
  170. {
  171. if (handle_)
  172. {
  173. shouldRun_ = false;
  174. // Create and delete a dummy file to make sure the watcher loop terminates
  175. String dummyFileName = path_ + "dummy.tmp";
  176. File file(context_, dummyFileName, FILE_WRITE);
  177. file.Close();
  178. if (fileSystem_)
  179. fileSystem_->Delete(dummyFileName);
  180. Stop();
  181. #if defined(WIN32)
  182. CloseHandle((HANDLE)dirHandle_);
  183. #elif defined(__linux__)
  184. for (HashMap<int, String>::Iterator i = dirHandle_.Begin(); i != dirHandle_.End(); ++i)
  185. inotify_rm_watch(watchHandle_, i->first_);
  186. dirHandle_.Clear();
  187. #elif defined(__APPLE__) && !defined(IOS)
  188. CloseFileWatcher(watcher_);
  189. #endif
  190. LOGDEBUG("Stopped watching path " + path_);
  191. path_.Clear();
  192. }
  193. }
  194. void FileWatcher::SetDelay(float interval)
  195. {
  196. delay_ = Max(interval, 0.0f);
  197. }
  198. void FileWatcher::ThreadFunction()
  199. {
  200. #if defined(URHO3D_FILEWATCHER)
  201. #if defined(WIN32)
  202. unsigned char buffer[BUFFERSIZE];
  203. DWORD bytesFilled = 0;
  204. while (shouldRun_)
  205. {
  206. if (ReadDirectoryChangesW((HANDLE)dirHandle_,
  207. buffer,
  208. BUFFERSIZE,
  209. watchSubDirs_,
  210. FILE_NOTIFY_CHANGE_FILE_NAME |
  211. FILE_NOTIFY_CHANGE_LAST_WRITE,
  212. &bytesFilled,
  213. 0,
  214. 0))
  215. {
  216. unsigned offset = 0;
  217. while (offset < bytesFilled)
  218. {
  219. FILE_NOTIFY_INFORMATION* record = (FILE_NOTIFY_INFORMATION*)&buffer[offset];
  220. if (record->Action == FILE_ACTION_MODIFIED || record->Action == FILE_ACTION_RENAMED_NEW_NAME)
  221. {
  222. String fileName;
  223. const wchar_t* src = record->FileName;
  224. const wchar_t* end = src + record->FileNameLength / 2;
  225. while (src < end)
  226. fileName.AppendUTF8(String::DecodeUTF16(src));
  227. fileName = GetInternalPath(fileName);
  228. AddChange(fileName);
  229. }
  230. if (!record->NextEntryOffset)
  231. break;
  232. else
  233. offset += record->NextEntryOffset;
  234. }
  235. }
  236. }
  237. #elif defined(__linux__)
  238. unsigned char buffer[BUFFERSIZE];
  239. while (shouldRun_)
  240. {
  241. int i = 0;
  242. int length = read(watchHandle_, buffer, sizeof(buffer));
  243. if (length < 0)
  244. return;
  245. while (i < length)
  246. {
  247. inotify_event* event = (inotify_event*)&buffer[i];
  248. if (event->len > 0)
  249. {
  250. if (event->mask & IN_MODIFY || event->mask & IN_MOVE)
  251. {
  252. String fileName;
  253. fileName = dirHandle_[event->wd] + event->name;
  254. AddChange(fileName);
  255. }
  256. }
  257. i += sizeof(inotify_event) + event->len;
  258. }
  259. }
  260. #elif defined(__APPLE__) && !defined(IOS)
  261. while (shouldRun_)
  262. {
  263. Time::Sleep(100);
  264. String changes = ReadFileWatcher(watcher_);
  265. if (!changes.Empty())
  266. {
  267. Vector<String> fileNames = changes.Split(1);
  268. for (unsigned i = 0; i < fileNames.Size(); ++i)
  269. AddChange(fileNames[i]);
  270. }
  271. }
  272. #endif
  273. #endif
  274. }
  275. void FileWatcher::AddChange(const String& fileName)
  276. {
  277. MutexLock lock(changesMutex_);
  278. // Reset the timer associated with the filename. Will be notified once timer exceeds the delay
  279. changes_[fileName].Reset();
  280. }
  281. bool FileWatcher::GetNextChange(String& dest)
  282. {
  283. MutexLock lock(changesMutex_);
  284. unsigned delayMsec = (unsigned)(delay_ * 1000.0f);
  285. if (changes_.Empty())
  286. return false;
  287. else
  288. {
  289. for (HashMap<String, Timer>::Iterator i = changes_.Begin(); i != changes_.End(); ++i)
  290. {
  291. if (i->second_.GetMSec(false) >= delayMsec)
  292. {
  293. dest = i->first_;
  294. changes_.Erase(i);
  295. return true;
  296. }
  297. }
  298. return false;
  299. }
  300. }
  301. }