BsLinuxFolderMonitor.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523
  1. //********************************** Banshee Engine (www.banshee3d.com) **************************************************//
  2. //**************** Copyright (c) 2016 Marko Pintera ([email protected]). All rights reserved. **********************//
  3. #include "Platform/BsFolderMonitor.h"
  4. #include "FileSystem/BsFileSystem.h"
  5. #include "Error/BsException.h"
  6. #include <sys/inotify.h>
  7. namespace bs
  8. {
  9. struct FolderMonitor::FolderWatchInfo
  10. {
  11. FolderWatchInfo(const Path& folderToMonitor, int inHandle, bool monitorSubdirectories, FolderChangeBits filter);
  12. ~FolderWatchInfo();
  13. void startMonitor();
  14. void stopMonitor();
  15. void addPath(const Path& path);
  16. void removePath(const Path& path);
  17. Path getPath(INT32 handle);
  18. Path folderToMonitor;
  19. int dirHandle;
  20. bool monitorSubdirectories;
  21. FolderChangeBits filter;
  22. UnorderedMap<Path, INT32> pathToHandle;
  23. UnorderedMap<INT32, Path> handleToPath;
  24. };
  25. FolderMonitor::FolderWatchInfo::FolderWatchInfo(const Path& folderToMonitor, int inHandle, bool monitorSubdirectories,
  26. FolderChangeBits filter)
  27. : folderToMonitor(folderToMonitor), dirHandle(0), monitorSubdirectories(monitorSubdirectories)
  28. , filter(filter)
  29. { }
  30. FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
  31. {
  32. stopMonitor();
  33. }
  34. void FolderMonitor::FolderWatchInfo::startMonitor()
  35. {
  36. addPath(folderToMonitor);
  37. if(monitorSubdirectories)
  38. {
  39. FileSystem::iterate(folderToMonitor, nullptr, [this](const Path& path)
  40. {
  41. addPath(path);
  42. return true;
  43. });
  44. }
  45. }
  46. void FolderMonitor::FolderWatchInfo::stopMonitor()
  47. {
  48. for(auto& entry : pathToHandle)
  49. inotify_rm_watch(dirHandle, entry.second);
  50. pathToHandle.clear();
  51. }
  52. void FolderMonitor::FolderWatchInfo::addPath(const Path& path)
  53. {
  54. String pathString = path.toString();
  55. INT32 watchHandle = inotify_add_watch(dirHandle, pathString.c_str(), IN_ALL_EVENTS);
  56. pathToHandle[path] = watchHandle;
  57. handleToPath[watchHandle] = path;
  58. }
  59. void FolderMonitor::FolderWatchInfo::removePath(const Path& path)
  60. {
  61. auto iterFind = pathToHandle.find(path);
  62. if(iterFind != pathToHandle.end())
  63. {
  64. INT32 watchHandle = iterFind->second;
  65. pathToHandle.erase(iterFind);
  66. handleToPath.erase(watchHandle);
  67. }
  68. }
  69. Path FolderMonitor::FolderWatchInfo::getPath(INT32 handle)
  70. {
  71. auto iterFind = handleToPath.find(handle);
  72. if(iterFind != handleToPath.end())
  73. return iterFind->second;
  74. return Path::BLANK;
  75. }
  76. class FolderMonitor::FileNotifyInfo
  77. {
  78. };
  79. enum class FileActionType
  80. {
  81. Added,
  82. Removed,
  83. Modified,
  84. Renamed
  85. };
  86. struct FileAction
  87. {
  88. static FileAction* createAdded(const WString& fileName)
  89. {
  90. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  91. FileAction* action = (FileAction*)bytes;
  92. bytes += sizeof(FileAction);
  93. action->oldName = nullptr;
  94. action->newName = (WString::value_type*)bytes;
  95. action->type = FileActionType::Added;
  96. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  97. action->newName[fileName.size()] = L'\0';
  98. action->lastSize = 0;
  99. action->checkForWriteStarted = false;
  100. return action;
  101. }
  102. static FileAction* createRemoved(const WString& fileName)
  103. {
  104. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  105. FileAction* action = (FileAction*)bytes;
  106. bytes += sizeof(FileAction);
  107. action->oldName = nullptr;
  108. action->newName = (WString::value_type*)bytes;
  109. action->type = FileActionType::Removed;
  110. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  111. action->newName[fileName.size()] = L'\0';
  112. action->lastSize = 0;
  113. action->checkForWriteStarted = false;
  114. return action;
  115. }
  116. static FileAction* createModified(const WString& fileName)
  117. {
  118. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  119. FileAction* action = (FileAction*)bytes;
  120. bytes += sizeof(FileAction);
  121. action->oldName = nullptr;
  122. action->newName = (WString::value_type*)bytes;
  123. action->type = FileActionType::Modified;
  124. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  125. action->newName[fileName.size()] = L'\0';
  126. action->lastSize = 0;
  127. action->checkForWriteStarted = false;
  128. return action;
  129. }
  130. static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
  131. {
  132. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) +
  133. (oldFilename.size() + newfileName.size() + 2) * sizeof(WString::value_type)));
  134. FileAction* action = (FileAction*)bytes;
  135. bytes += sizeof(FileAction);
  136. action->oldName = (WString::value_type*)bytes;
  137. bytes += (oldFilename.size() + 1) * sizeof(WString::value_type);
  138. action->newName = (WString::value_type*)bytes;
  139. action->type = FileActionType::Modified;
  140. memcpy(action->oldName, oldFilename.data(), oldFilename.size() * sizeof(WString::value_type));
  141. action->oldName[oldFilename.size()] = L'\0';
  142. memcpy(action->newName, newfileName.data(), newfileName.size() * sizeof(WString::value_type));
  143. action->newName[newfileName.size()] = L'\0';
  144. action->lastSize = 0;
  145. action->checkForWriteStarted = false;
  146. return action;
  147. }
  148. static void destroy(FileAction* action)
  149. {
  150. bs_free(action);
  151. }
  152. WString::value_type* oldName;
  153. WString::value_type* newName;
  154. FileActionType type;
  155. UINT64 lastSize;
  156. bool checkForWriteStarted;
  157. };
  158. struct FolderMonitor::Pimpl
  159. {
  160. Vector<FolderWatchInfo*> monitors;
  161. Vector<FileAction*> fileActions;
  162. Vector<FileAction*> activeFileActions;
  163. int inHandle;
  164. bool started;
  165. Mutex mainMutex;
  166. Thread* workerThread;
  167. };
  168. FolderMonitor::FolderMonitor()
  169. {
  170. m = bs_new<Pimpl>();
  171. m->workerThread = nullptr;
  172. m->inHandle = 0;
  173. m->started = false;
  174. }
  175. FolderMonitor::~FolderMonitor()
  176. {
  177. stopMonitorAll();
  178. // No need for mutex since we know worker thread is shut down by now
  179. for(auto& action : m->fileActions)
  180. FileAction::destroy(action);
  181. bs_delete(m);
  182. }
  183. void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChangeBits changeFilter)
  184. {
  185. if(!FileSystem::isDirectory(folderPath))
  186. {
  187. LOGERR("Provided path \"" + folderPath.toString() + "\" is not a directory");
  188. return;
  189. }
  190. // Check if there is overlap with existing monitors
  191. for(auto& monitor : m->monitors)
  192. {
  193. // Identical monitor exists
  194. if(monitor->folderToMonitor.equals(folderPath))
  195. {
  196. LOGWRN("Folder is already monitored, cannot monitor it again.");
  197. return;
  198. }
  199. // This directory is part of a directory that's being monitored
  200. if(monitor->monitorSubdirectories && folderPath.includes(monitor->folderToMonitor))
  201. {
  202. LOGWRN("Folder is already monitored, cannot monitor it again.");
  203. return;
  204. }
  205. // This directory would include a directory of another monitor
  206. if(subdirectories && monitor->folderToMonitor.includes(folderPath))
  207. {
  208. LOGWRN("Cannot add a recursive monitor as it conflicts with a previously monitored path");
  209. return;
  210. }
  211. }
  212. // Initialize inotify if required
  213. if(!m->started)
  214. {
  215. Lock lock(m->mainMutex);
  216. m->inHandle = inotify_init();
  217. m->started = true;
  218. }
  219. FolderWatchInfo* watchInfo = bs_new<FolderWatchInfo>(folderPath, m->inHandle, subdirectories, changeFilter);
  220. // Register and start the monitor
  221. {
  222. Lock lock(m->mainMutex);
  223. m->monitors.push_back(watchInfo);
  224. watchInfo->startMonitor();
  225. }
  226. // Start the worker thread if it isn't already
  227. if(m->workerThread == nullptr)
  228. {
  229. m->workerThread = bs_new<Thread>(std::bind(&FolderMonitor::workerThreadMain, this));
  230. if(m->workerThread == nullptr)
  231. LOGERR("Failed to create a new worker thread for folder monitoring");
  232. }
  233. }
  234. void FolderMonitor::stopMonitor(const Path& folderPath)
  235. {
  236. auto findIter = std::find_if(m->monitors.begin(), m->monitors.end(),
  237. [&](const FolderWatchInfo* x) { return x->folderToMonitor == folderPath; });
  238. if(findIter != m->monitors.end())
  239. {
  240. Lock lock(m->mainMutex);
  241. FolderWatchInfo* watchInfo = *findIter;
  242. watchInfo->stopMonitor();
  243. bs_delete(watchInfo);
  244. m->monitors.erase(findIter);
  245. }
  246. if(m->monitors.size() == 0)
  247. stopMonitorAll();
  248. }
  249. void FolderMonitor::stopMonitorAll()
  250. {
  251. {
  252. Lock lock(m->mainMutex);
  253. for (auto& watchInfo : m->monitors)
  254. {
  255. watchInfo->stopMonitor();
  256. bs_delete(watchInfo);
  257. }
  258. m->monitors.clear();
  259. }
  260. {
  261. Lock lock(m->mainMutex);
  262. if (m->started)
  263. {
  264. // This should trigger worker thread shut-down
  265. close(m->inHandle);
  266. m->inHandle = 0;
  267. m->started = false;
  268. }
  269. }
  270. if(m->workerThread != nullptr)
  271. {
  272. m->workerThread->join();
  273. bs_delete(m->workerThread);
  274. m->workerThread = nullptr;
  275. }
  276. }
  277. void FolderMonitor::workerThreadMain()
  278. {
  279. static const UINT32 BUFFER_SIZE = 16384;
  280. INT32 watchHandle;
  281. {
  282. Lock(m->mainMutex);
  283. watchHandle = m->inHandle;
  284. }
  285. UINT8 buffer[BUFFER_SIZE];
  286. while(true)
  287. {
  288. INT32 length = (INT32)read(watchHandle, buffer, sizeof(buffer));
  289. // Handle was closed, shutdown thread
  290. if (length < 0)
  291. return;
  292. INT32 readPos = 0;
  293. while(readPos < length)
  294. {
  295. inotify_event* event = (inotify_event*)&buffer[readPos];
  296. if(event->len > 0)
  297. {
  298. {
  299. Lock lock(m->mainMutex);
  300. Path path;
  301. FolderWatchInfo* monitor = nullptr;
  302. for (auto& entry : m->monitors)
  303. {
  304. path = entry->getPath(event->wd);
  305. if (!path.isEmpty())
  306. {
  307. path.append(event->name);
  308. monitor = entry;
  309. break;
  310. }
  311. }
  312. // This can happen if the path got removed during some recent previous event
  313. if(monitor == nullptr)
  314. goto next;
  315. // Need to add/remove sub-directories to/from watch list
  316. bool isDirectory = (event->mask & IN_ISDIR) != 0;
  317. if(isDirectory && monitor->monitorSubdirectories)
  318. {
  319. bool added = (event->mask & (IN_CREATE | IN_MOVED_TO)) != 0;
  320. bool removed = (event->mask & (IN_DELETE | IN_MOVED_FROM)) != 0;
  321. if(added)
  322. monitor->addPath(path);
  323. else if(removed)
  324. monitor->removePath(path);
  325. }
  326. // Actually trigger the events
  327. // File/folder was added
  328. if(((event->mask & (IN_CREATE | IN_MOVED_TO)) != 0))
  329. {
  330. if (isDirectory)
  331. {
  332. if (monitor->filter.isSet(FolderChangeBit::DirName))
  333. m->fileActions.push_back(FileAction::createAdded(path.toWString()));
  334. }
  335. else
  336. {
  337. if (monitor->filter.isSet(FolderChangeBit::FileName))
  338. m->fileActions.push_back(FileAction::createAdded(path.toWString()));
  339. }
  340. }
  341. // File/folder was removed
  342. if(((event->mask & (IN_DELETE | IN_MOVED_FROM)) != 0))
  343. {
  344. if(isDirectory)
  345. {
  346. if(monitor->filter.isSet(FolderChangeBit::DirName))
  347. m->fileActions.push_back(FileAction::createRemoved(path.toWString()));
  348. }
  349. else
  350. {
  351. if(monitor->filter.isSet(FolderChangeBit::FileName))
  352. m->fileActions.push_back(FileAction::createRemoved(path.toWString()));
  353. }
  354. }
  355. // File was modified
  356. if(((event->mask & IN_CLOSE_WRITE) != 0) && monitor->filter.isSet(FolderChangeBit::FileWrite))
  357. {
  358. m->fileActions.push_back(FileAction::createModified(path.toWString()));
  359. }
  360. // Note: Not reporting renames, instead a remove + add event is created. To support renames I'd need
  361. // to defer all event triggering until I have processed move event pairs and determined if the
  362. // move is a rename (i.e. parent folder didn't change). All events need to be deferred (not just
  363. // move events) in order to preserve the event ordering. For now this is too much hassle considering
  364. // no external code relies on the rename functionality.
  365. }
  366. }
  367. next:
  368. readPos += sizeof(inotify_event) + event->len;
  369. }
  370. }
  371. }
  372. void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
  373. {
  374. // Do nothing
  375. }
  376. void FolderMonitor::_update()
  377. {
  378. {
  379. Lock lock(m->mainMutex);
  380. std::swap(m->fileActions, m->activeFileActions);
  381. }
  382. for(auto& action : m->activeFileActions)
  383. {
  384. // Reported file actions might still be in progress (i.e. something might still be writing to those files).
  385. // Sadly there doesn't seem to be a way to properly determine when those files are done being written, so instead
  386. // we check for at least a couple of frames if the file's size hasn't changed before reporting a file action.
  387. // This takes care of most of the issues and avoids reporting partially written files in almost all cases.
  388. if (FileSystem::exists(action->newName))
  389. {
  390. UINT64 size = FileSystem::getFileSize(action->newName);
  391. if (!action->checkForWriteStarted)
  392. {
  393. action->checkForWriteStarted = true;
  394. action->lastSize = size;
  395. continue;
  396. }
  397. else
  398. {
  399. if (action->lastSize != size)
  400. {
  401. action->lastSize = size;
  402. continue;
  403. }
  404. }
  405. }
  406. switch (action->type)
  407. {
  408. case FileActionType::Added:
  409. if (!onAdded.empty())
  410. onAdded(Path(action->newName));
  411. break;
  412. case FileActionType::Removed:
  413. if (!onRemoved.empty())
  414. onRemoved(Path(action->newName));
  415. break;
  416. case FileActionType::Modified:
  417. if (!onModified.empty())
  418. onModified(Path(action->newName));
  419. break;
  420. case FileActionType::Renamed:
  421. if (!onRenamed.empty())
  422. onRenamed(Path(action->oldName), Path(action->newName));
  423. break;
  424. }
  425. FileAction::destroy(action);
  426. }
  427. m->activeFileActions.clear();
  428. }
  429. }