BsMacOSFolderMonitor.cpp 15 KB

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