BsMacOSFolderMonitor.cpp 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  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 <CoreServices/CoreServices.h>
  7. namespace bs
  8. {
  9. CFStringRef FolderMonitorMode = CFSTR("BSFolderMonitor");
  10. enum class FileActionType
  11. {
  12. Added,
  13. Removed,
  14. Modified,
  15. Renamed
  16. };
  17. struct FileAction
  18. {
  19. static FileAction* createAdded(const WString& fileName)
  20. {
  21. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  22. FileAction* action = (FileAction*)bytes;
  23. bytes += sizeof(FileAction);
  24. action->oldName = nullptr;
  25. action->newName = (WString::value_type*)bytes;
  26. action->type = FileActionType::Added;
  27. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  28. action->newName[fileName.size()] = L'\0';
  29. return action;
  30. }
  31. static FileAction* createRemoved(const WString& fileName)
  32. {
  33. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  34. FileAction* action = (FileAction*)bytes;
  35. bytes += sizeof(FileAction);
  36. action->oldName = nullptr;
  37. action->newName = (WString::value_type*)bytes;
  38. action->type = FileActionType::Removed;
  39. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  40. action->newName[fileName.size()] = L'\0';
  41. return action;
  42. }
  43. static FileAction* createModified(const WString& fileName)
  44. {
  45. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  46. FileAction* action = (FileAction*)bytes;
  47. bytes += sizeof(FileAction);
  48. action->oldName = nullptr;
  49. action->newName = (WString::value_type*)bytes;
  50. action->type = FileActionType::Modified;
  51. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  52. action->newName[fileName.size()] = L'\0';
  53. return action;
  54. }
  55. static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
  56. {
  57. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) +
  58. (oldFilename.size() + newfileName.size() + 2) * sizeof(WString::value_type)));
  59. FileAction* action = (FileAction*)bytes;
  60. bytes += sizeof(FileAction);
  61. action->oldName = (WString::value_type*)bytes;
  62. bytes += (oldFilename.size() + 1) * sizeof(WString::value_type);
  63. action->newName = (WString::value_type*)bytes;
  64. action->type = FileActionType::Modified;
  65. memcpy(action->oldName, oldFilename.data(), oldFilename.size() * sizeof(WString::value_type));
  66. action->oldName[oldFilename.size()] = L'\0';
  67. memcpy(action->newName, newfileName.data(), newfileName.size() * sizeof(WString::value_type));
  68. action->newName[newfileName.size()] = L'\0';
  69. return action;
  70. }
  71. static void destroy(FileAction* action)
  72. {
  73. bs_free(action);
  74. }
  75. WString::value_type* oldName;
  76. WString::value_type* newName;
  77. FileActionType type;
  78. };
  79. struct FolderMonitor::Pimpl
  80. {
  81. Vector<FolderWatchInfo*> monitorsToStart;
  82. Vector<FolderWatchInfo*> monitorsToStop;
  83. Vector<FolderWatchInfo*> monitors;
  84. Vector<FileAction*> fileActions;
  85. Vector<FileAction*> activeFileActions;
  86. Mutex mainMutex;
  87. Thread* workerThread;
  88. };
  89. static void watcherCallback(ConstFSEventStreamRef streamRef, void* userInfo, size_t numEvents, void* eventPaths,
  90. const FSEventStreamEventFlags* eventFlags, const FSEventStreamEventId* eventIds);
  91. struct FolderMonitor::FolderWatchInfo
  92. {
  93. FolderWatchInfo(
  94. const Path& folderToMonitor,
  95. FolderMonitor* owner,
  96. bool monitorSubdirectories,
  97. FolderChangeBits filter);
  98. ~FolderWatchInfo();
  99. void startMonitor();
  100. void stopMonitor();
  101. Path folderToMonitor;
  102. FolderMonitor* owner;
  103. bool monitorSubdirectories;
  104. FolderChangeBits filter;
  105. FSEventStreamRef streamRef;
  106. bool hasStarted;
  107. UnorderedSet<FSEventStreamEventId> renameEvents;
  108. };
  109. FolderMonitor::FolderWatchInfo::FolderWatchInfo(
  110. const Path& folderToMonitor,
  111. FolderMonitor* owner,
  112. bool monitorSubdirectories,
  113. FolderChangeBits filter)
  114. : folderToMonitor(folderToMonitor)
  115. , owner(owner)
  116. , monitorSubdirectories(monitorSubdirectories)
  117. , filter(filter)
  118. , streamRef(nullptr)
  119. , hasStarted(false)
  120. { }
  121. FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
  122. {
  123. stopMonitor();
  124. }
  125. void FolderMonitor::FolderWatchInfo::startMonitor()
  126. {
  127. String pathString = folderToMonitor.toString();
  128. CFStringRef path = CFStringCreateWithCString(kCFAllocatorDefault, pathString.c_str(), kCFStringEncodingUTF8);
  129. CFArrayRef pathArray = CFArrayCreate(nullptr, (const void **)&path, 1, nullptr);
  130. FSEventStreamContext context = {};
  131. context.info = this;
  132. CFAbsoluteTime latency = 0.1f;
  133. streamRef = FSEventStreamCreate(
  134. kCFAllocatorDefault,
  135. &watcherCallback,
  136. &context,
  137. pathArray,
  138. kFSEventStreamEventIdSinceNow,
  139. latency,
  140. kFSEventStreamCreateFlagUseCFTypes | kFSEventStreamCreateFlagFileEvents);
  141. CFRelease(pathArray);
  142. if (streamRef)
  143. {
  144. FSEventStreamScheduleWithRunLoop(streamRef, CFRunLoopGetMain(), FolderMonitorMode);
  145. if(FSEventStreamStart(streamRef))
  146. hasStarted = true;
  147. }
  148. CFRelease(path);
  149. }
  150. void FolderMonitor::FolderWatchInfo::stopMonitor()
  151. {
  152. if(!streamRef)
  153. return;
  154. if(hasStarted)
  155. {
  156. FSEventStreamStop(streamRef);
  157. hasStarted = false;
  158. }
  159. FSEventStreamInvalidate(streamRef);
  160. FSEventStreamRelease(streamRef);
  161. }
  162. static void watcherCallback(ConstFSEventStreamRef streamRef, void* userInfo, size_t numEvents, void* eventPaths,
  163. const FSEventStreamEventFlags* eventFlags, const FSEventStreamEventId* eventIds)
  164. {
  165. auto* watcher = (FolderMonitor::FolderWatchInfo*)userInfo;
  166. FolderMonitor::Pimpl* folderData = watcher->owner->_getPrivateData();
  167. auto paths = (CFArrayRef)eventPaths;
  168. CFIndex length = CFArrayGetCount(paths);
  169. for(CFIndex i = 0; i < length; i++)
  170. {
  171. auto pathEntry = (CFStringRef)CFArrayGetValueAtIndex(paths, i);
  172. Path path = CFStringGetCStringPtr(pathEntry, kCFStringEncodingUTF8);
  173. CFIndex pathLength = CFStringGetLength(pathEntry);
  174. if(pathLength == 0)
  175. continue;
  176. Lock lock(folderData->mainMutex);
  177. // If not monitoring subdirectories, ignore paths that aren't direct descendants of the root path
  178. if(!watcher->monitorSubdirectories)
  179. {
  180. if(path.getParent() != watcher->folderToMonitor)
  181. continue;
  182. }
  183. FSEventStreamEventFlags flags = eventFlags[i];
  184. bool isFile = (flags & kFSEventStreamEventFlagItemIsFile) != 0;
  185. bool wasCreated = (flags & kFSEventStreamEventFlagItemCreated) != 0;
  186. bool wasModified = (flags & kFSEventStreamEventFlagItemModified) != 0;
  187. bool wasRemoved = (flags & kFSEventStreamEventFlagItemRemoved) != 0;
  188. // Rename events get translated to create/remove events
  189. bool wasRenamed = (flags & kFSEventStreamEventFlagItemRenamed) != 0;
  190. if(wasRenamed)
  191. {
  192. auto iterFind = watcher->renameEvents.find(eventIds[i]);
  193. if (iterFind == watcher->renameEvents.end())
  194. {
  195. wasRemoved = true;
  196. watcher->renameEvents.insert(eventIds[i]);
  197. }
  198. else
  199. {
  200. wasCreated = true;
  201. watcher->renameEvents.erase(eventIds[i]);
  202. }
  203. }
  204. // File/folder was added
  205. if(wasCreated)
  206. {
  207. if (!isFile)
  208. {
  209. if (watcher->filter.isSet(FolderChangeBit::DirName))
  210. folderData->fileActions.push_back(FileAction::createAdded(path.toWString()));
  211. }
  212. else
  213. {
  214. if (watcher->filter.isSet(FolderChangeBit::FileName))
  215. folderData->fileActions.push_back(FileAction::createAdded(path.toWString()));
  216. }
  217. }
  218. // File/folder was removed
  219. if(wasRemoved)
  220. {
  221. if(!isFile)
  222. {
  223. if(watcher->filter.isSet(FolderChangeBit::DirName))
  224. folderData->fileActions.push_back(FileAction::createRemoved(path.toWString()));
  225. }
  226. else
  227. {
  228. if(watcher->filter.isSet(FolderChangeBit::FileName))
  229. folderData->fileActions.push_back(FileAction::createRemoved(path.toWString()));
  230. }
  231. }
  232. // File was modified
  233. if(wasModified && watcher->filter.isSet(FolderChangeBit::FileWrite))
  234. {
  235. folderData->fileActions.push_back(FileAction::createModified(path.toWString()));
  236. }
  237. }
  238. }
  239. class FolderMonitor::FileNotifyInfo
  240. {
  241. };
  242. FolderMonitor::FolderMonitor()
  243. {
  244. m = bs_new<Pimpl>();
  245. m->workerThread = nullptr;
  246. }
  247. FolderMonitor::~FolderMonitor()
  248. {
  249. stopMonitorAll();
  250. // No need for mutex since we know worker thread is shut down by now
  251. for(auto& action : m->fileActions)
  252. FileAction::destroy(action);
  253. bs_delete(m);
  254. }
  255. void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChangeBits changeFilter)
  256. {
  257. if(!FileSystem::isDirectory(folderPath))
  258. {
  259. LOGERR("Provided path \"" + folderPath.toString() + "\" is not a directory.");
  260. return;
  261. }
  262. // Check if there is overlap with existing monitors
  263. for(auto& monitor : m->monitors)
  264. {
  265. // Identical monitor exists
  266. if(monitor->folderToMonitor.equals(folderPath))
  267. {
  268. LOGWRN("Folder is already monitored, cannot monitor it again.");
  269. return;
  270. }
  271. // This directory is part of a directory that's being monitored
  272. if(monitor->monitorSubdirectories && folderPath.includes(monitor->folderToMonitor))
  273. {
  274. LOGWRN("Folder is already monitored, cannot monitor it again.");
  275. return;
  276. }
  277. // This directory would include a directory of another monitor
  278. if(subdirectories && monitor->folderToMonitor.includes(folderPath))
  279. {
  280. LOGWRN("Cannot add a recursive monitor as it conflicts with a previously monitored path.");
  281. return;
  282. }
  283. }
  284. auto watchInfo = bs_new<FolderWatchInfo>(folderPath, this, subdirectories, changeFilter);
  285. // Register and start the monitor
  286. {
  287. Lock lock(m->mainMutex);
  288. m->monitorsToStart.push_back(watchInfo);
  289. m->monitors.push_back(watchInfo);
  290. }
  291. // Start the worker thread if it isn't already
  292. if(m->workerThread == nullptr)
  293. {
  294. m->workerThread = bs_new<Thread>(std::bind(&FolderMonitor::workerThreadMain, this));
  295. if(m->workerThread == nullptr)
  296. LOGERR("Failed to create a new worker thread for folder monitoring");
  297. }
  298. }
  299. void FolderMonitor::stopMonitor(const Path& folderPath)
  300. {
  301. auto findIter = std::find_if(m->monitors.begin(), m->monitors.end(),
  302. [&](const FolderWatchInfo* x) { return x->folderToMonitor == folderPath; });
  303. if(findIter != m->monitors.end())
  304. {
  305. // Special case if this is the last monitor
  306. if(m->monitors.size() == 1)
  307. stopMonitorAll();
  308. else
  309. {
  310. Lock lock(m->mainMutex);
  311. FolderWatchInfo* watchInfo = *findIter;
  312. watchInfo->stopMonitor();
  313. m->monitorsToStop.push_back(watchInfo);
  314. m->monitors.erase(findIter);
  315. }
  316. }
  317. }
  318. void FolderMonitor::stopMonitorAll()
  319. {
  320. {
  321. Lock lock(m->mainMutex);
  322. // Remove all watches (this will also wake up the thread)
  323. for (auto& watchInfo : m->monitors)
  324. {
  325. watchInfo->stopMonitor();
  326. m->monitorsToStop.push_back(watchInfo);
  327. }
  328. m->monitors.clear();
  329. }
  330. // Wait for the thread to shutdown
  331. if(m->workerThread != nullptr)
  332. {
  333. m->workerThread->join();
  334. bs_delete(m->workerThread);
  335. m->workerThread = nullptr;
  336. }
  337. }
  338. void FolderMonitor::workerThreadMain()
  339. {
  340. while(true)
  341. {
  342. // Start up any newly added monitors
  343. {
  344. Lock lock(m->mainMutex);
  345. for(auto& entry : m->monitorsToStart)
  346. entry->startMonitor();
  347. m->monitorsToStart.clear();
  348. }
  349. // Run the loop in order to receive events
  350. INT32 result = CFRunLoopRunInMode(FolderMonitorMode, 0.1f, false);
  351. // Delete any stopped monitors
  352. {
  353. Lock lock(m->mainMutex);
  354. for (auto& entry : m->monitorsToStop)
  355. bs_delete(entry);
  356. m->monitorsToStop.clear();
  357. }
  358. // All input sources removed, or explicitly stopped, bail
  359. if((result == kCFRunLoopRunStopped) || (result == kCFRunLoopRunFinished))
  360. break;
  361. // It's possible some system registered an input source with our loop, in which case the above check will not
  362. // work. Instead check if there are any monitors left.
  363. // Note: In this case we may also pay a 0.1 second timeout cost, since we don't explicitly wake the run loop.
  364. // Ideally we would also wake the run loop from the main thread so it is able to exit immediately.
  365. {
  366. Lock lock(m->mainMutex);
  367. if(m->monitors.empty())
  368. break;
  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. switch (action->type)
  385. {
  386. case FileActionType::Added:
  387. if (!onAdded.empty())
  388. onAdded(Path(action->newName));
  389. break;
  390. case FileActionType::Removed:
  391. if (!onRemoved.empty())
  392. onRemoved(Path(action->newName));
  393. break;
  394. case FileActionType::Modified:
  395. if (!onModified.empty())
  396. onModified(Path(action->newName));
  397. break;
  398. case FileActionType::Renamed:
  399. if (!onRenamed.empty())
  400. onRenamed(Path(action->oldName), Path(action->newName));
  401. break;
  402. }
  403. FileAction::destroy(action);
  404. }
  405. m->activeFileActions.clear();
  406. }
  407. }