FileWatcher.cpp 9.4 KB

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