FileWatcher.cpp 9.7 KB

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