BsWin32FolderMonitor.cpp 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694
  1. #include "Win32/BsWin32FolderMonitor.h"
  2. #include "BsFileSystem.h"
  3. #include "BsException.h"
  4. #include "BsDebug.h"
  5. #include <windows.h>
  6. namespace BansheeEngine
  7. {
  8. enum class MonitorState
  9. {
  10. Inactive,
  11. Starting,
  12. Monitoring,
  13. Shutdown,
  14. Shutdown2
  15. };
  16. class WorkerFunc
  17. {
  18. public:
  19. WorkerFunc(FolderMonitor* owner);
  20. void operator()();
  21. private:
  22. FolderMonitor* mOwner;
  23. };
  24. struct FolderMonitor::FolderWatchInfo
  25. {
  26. FolderWatchInfo(const Path& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags);
  27. ~FolderWatchInfo();
  28. void startMonitor(HANDLE compPortHandle);
  29. void stopMonitor(HANDLE compPortHandle);
  30. static const UINT32 READ_BUFFER_SIZE = 65536;
  31. Path mFolderToMonitor;
  32. HANDLE mDirHandle;
  33. OVERLAPPED mOverlapped;
  34. MonitorState mState;
  35. UINT8 mBuffer[READ_BUFFER_SIZE];
  36. DWORD mBufferSize;
  37. bool mMonitorSubdirectories;
  38. DWORD mMonitorFlags;
  39. DWORD mReadError;
  40. WString mCachedOldFileName; // Used during rename notifications as they are handled in two steps
  41. BS_MUTEX(mStatusMutex)
  42. BS_THREAD_SYNCHRONISER(mStartStopEvent)
  43. };
  44. FolderMonitor::FolderWatchInfo::FolderWatchInfo(const Path& folderToMonitor, HANDLE dirHandle, bool monitorSubdirectories, DWORD monitorFlags)
  45. :mFolderToMonitor(folderToMonitor), mDirHandle(dirHandle), mState(MonitorState::Inactive), mBufferSize(0),
  46. mMonitorSubdirectories(monitorSubdirectories), mMonitorFlags(monitorFlags), mReadError(0)
  47. {
  48. memset(&mOverlapped, 0, sizeof(mOverlapped));
  49. }
  50. FolderMonitor::FolderWatchInfo::~FolderWatchInfo()
  51. {
  52. assert(mState == MonitorState::Inactive);
  53. stopMonitor(0);
  54. }
  55. void FolderMonitor::FolderWatchInfo::startMonitor(HANDLE compPortHandle)
  56. {
  57. if(mState != MonitorState::Inactive)
  58. return; // Already monitoring
  59. {
  60. BS_LOCK_MUTEX_NAMED(mStatusMutex, lock);
  61. mState = MonitorState::Starting;
  62. PostQueuedCompletionStatus(compPortHandle, sizeof(this), (ULONG_PTR)this, &mOverlapped);
  63. while(mState != MonitorState::Monitoring)
  64. BS_THREAD_WAIT(mStartStopEvent, mStatusMutex, lock);
  65. }
  66. if(mReadError != ERROR_SUCCESS)
  67. {
  68. {
  69. BS_LOCK_MUTEX(mStatusMutex);
  70. mState = MonitorState::Inactive;
  71. }
  72. BS_EXCEPT(InternalErrorException, "Failed to start folder monitor on folder \"" +
  73. mFolderToMonitor.toString() + "\" because ReadDirectoryChangesW failed.");
  74. }
  75. }
  76. void FolderMonitor::FolderWatchInfo::stopMonitor(HANDLE compPortHandle)
  77. {
  78. if(mState != MonitorState::Inactive)
  79. {
  80. BS_LOCK_MUTEX_NAMED(mStatusMutex, lock);
  81. mState = MonitorState::Shutdown;
  82. PostQueuedCompletionStatus(compPortHandle, sizeof(this), (ULONG_PTR)this, &mOverlapped);
  83. while(mState != MonitorState::Inactive)
  84. BS_THREAD_WAIT(mStartStopEvent, mStatusMutex, lock);
  85. }
  86. if(mDirHandle != INVALID_HANDLE_VALUE)
  87. {
  88. CloseHandle(mDirHandle);
  89. mDirHandle = INVALID_HANDLE_VALUE;
  90. }
  91. }
  92. class FolderMonitor::FileNotifyInfo
  93. {
  94. public:
  95. FileNotifyInfo(UINT8* notifyBuffer, DWORD bufferSize)
  96. :mBuffer(notifyBuffer), mBufferSize(bufferSize)
  97. {
  98. mCurrentRecord = (PFILE_NOTIFY_INFORMATION)mBuffer;
  99. }
  100. bool getNext();
  101. DWORD getAction() const;
  102. WString getFileName() const;
  103. WString getFileNameWithPath(const Path& rootPath) const;
  104. protected:
  105. UINT8* mBuffer;
  106. DWORD mBufferSize;
  107. PFILE_NOTIFY_INFORMATION mCurrentRecord;
  108. };
  109. bool FolderMonitor::FileNotifyInfo::getNext()
  110. {
  111. if(mCurrentRecord && mCurrentRecord->NextEntryOffset != 0)
  112. {
  113. PFILE_NOTIFY_INFORMATION oldRecord = mCurrentRecord;
  114. mCurrentRecord = (PFILE_NOTIFY_INFORMATION) ((UINT8*)mCurrentRecord + mCurrentRecord->NextEntryOffset);
  115. if((DWORD)((UINT8*)mCurrentRecord - mBuffer) > mBufferSize)
  116. {
  117. // Gone out of range, something bad happened
  118. assert(false);
  119. mCurrentRecord = oldRecord;
  120. }
  121. return (mCurrentRecord != oldRecord);
  122. }
  123. return false;
  124. }
  125. DWORD FolderMonitor::FileNotifyInfo::getAction() const
  126. {
  127. assert(mCurrentRecord != nullptr);
  128. if(mCurrentRecord)
  129. return mCurrentRecord->Action;
  130. return 0;
  131. }
  132. WString FolderMonitor::FileNotifyInfo::getFileName() const
  133. {
  134. if(mCurrentRecord)
  135. {
  136. wchar_t fileNameBuffer[32768 + 1] = {0};
  137. memcpy(fileNameBuffer, mCurrentRecord->FileName,
  138. std::min(DWORD(32768 * sizeof(wchar_t)), mCurrentRecord->FileNameLength));
  139. return WString(fileNameBuffer);
  140. }
  141. return WString();
  142. }
  143. WString FolderMonitor::FileNotifyInfo::getFileNameWithPath(const Path& rootPath) const
  144. {
  145. Path fullPath = rootPath;
  146. return fullPath.append(getFileName()).toWString();
  147. }
  148. enum class FileActionType
  149. {
  150. Added,
  151. Removed,
  152. Modified,
  153. Renamed
  154. };
  155. struct FileAction
  156. {
  157. static FileAction* createAdded(const WString& fileName)
  158. {
  159. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  160. FileAction* action = (FileAction*)bytes;
  161. bytes += sizeof(FileAction);
  162. action->oldName = nullptr;
  163. action->newName = (WString::value_type*)bytes;
  164. action->type = FileActionType::Added;
  165. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  166. action->newName[fileName.size()] = L'\0';
  167. action->lastSize = 0;
  168. action->checkForWriteStarted = false;
  169. return action;
  170. }
  171. static FileAction* createRemoved(const WString& fileName)
  172. {
  173. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  174. FileAction* action = (FileAction*)bytes;
  175. bytes += sizeof(FileAction);
  176. action->oldName = nullptr;
  177. action->newName = (WString::value_type*)bytes;
  178. action->type = FileActionType::Removed;
  179. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  180. action->newName[fileName.size()] = L'\0';
  181. action->lastSize = 0;
  182. action->checkForWriteStarted = false;
  183. return action;
  184. }
  185. static FileAction* createModified(const WString& fileName)
  186. {
  187. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) + (fileName.size() + 1) * sizeof(WString::value_type)));
  188. FileAction* action = (FileAction*)bytes;
  189. bytes += sizeof(FileAction);
  190. action->oldName = nullptr;
  191. action->newName = (WString::value_type*)bytes;
  192. action->type = FileActionType::Modified;
  193. memcpy(action->newName, fileName.data(), fileName.size() * sizeof(WString::value_type));
  194. action->newName[fileName.size()] = L'\0';
  195. action->lastSize = 0;
  196. action->checkForWriteStarted = false;
  197. return action;
  198. }
  199. static FileAction* createRenamed(const WString& oldFilename, const WString& newfileName)
  200. {
  201. UINT8* bytes = (UINT8*)bs_alloc((UINT32)(sizeof(FileAction) +
  202. (oldFilename.size() + newfileName.size() + 2) * sizeof(WString::value_type)));
  203. FileAction* action = (FileAction*)bytes;
  204. bytes += sizeof(FileAction);
  205. action->oldName = (WString::value_type*)bytes;
  206. bytes += (oldFilename.size() + 1) * sizeof(WString::value_type);
  207. action->newName = (WString::value_type*)bytes;
  208. action->type = FileActionType::Modified;
  209. memcpy(action->oldName, oldFilename.data(), oldFilename.size() * sizeof(WString::value_type));
  210. action->oldName[oldFilename.size()] = L'\0';
  211. memcpy(action->newName, newfileName.data(), newfileName.size() * sizeof(WString::value_type));
  212. action->newName[newfileName.size()] = L'\0';
  213. action->lastSize = 0;
  214. action->checkForWriteStarted = false;
  215. return action;
  216. }
  217. static void destroy(FileAction* action)
  218. {
  219. bs_free(action);
  220. }
  221. WString::value_type* oldName;
  222. WString::value_type* newName;
  223. FileActionType type;
  224. UINT64 lastSize;
  225. bool checkForWriteStarted;
  226. };
  227. struct FolderMonitor::Pimpl
  228. {
  229. Vector<FolderWatchInfo*> mFoldersToWatch;
  230. HANDLE mCompPortHandle;
  231. Queue<FileAction*> mFileActions;
  232. List<FileAction*> mActiveFileActions;
  233. BS_MUTEX(mMainMutex);
  234. BS_THREAD_TYPE* mWorkerThread;
  235. };
  236. FolderMonitor::FolderMonitor()
  237. {
  238. mPimpl = bs_new<Pimpl>();
  239. mPimpl->mWorkerThread = nullptr;
  240. mPimpl->mCompPortHandle = nullptr;
  241. }
  242. FolderMonitor::~FolderMonitor()
  243. {
  244. stopMonitorAll();
  245. // No need for mutex since we know worker thread is shut down by now
  246. while(!mPimpl->mFileActions.empty())
  247. {
  248. FileAction* action = mPimpl->mFileActions.front();
  249. mPimpl->mFileActions.pop();
  250. FileAction::destroy(action);
  251. }
  252. bs_delete(mPimpl);
  253. }
  254. void FolderMonitor::startMonitor(const Path& folderPath, bool subdirectories, FolderChange changeFilter)
  255. {
  256. if(!FileSystem::isDirectory(folderPath))
  257. {
  258. BS_EXCEPT(InvalidParametersException, "Provided path \"" + folderPath.toString() + "\" is not a directory");
  259. }
  260. WString extendedFolderPath = L"\\\\?\\" + folderPath.toWString(Path::PathType::Windows);
  261. HANDLE dirHandle = CreateFileW(extendedFolderPath.c_str(), FILE_LIST_DIRECTORY,
  262. FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, nullptr, OPEN_EXISTING,
  263. FILE_FLAG_BACKUP_SEMANTICS | FILE_FLAG_OVERLAPPED, nullptr);
  264. if(dirHandle == INVALID_HANDLE_VALUE)
  265. {
  266. BS_EXCEPT(InternalErrorException, "Failed to open folder \"" + folderPath.toString() + "\" for monitoring. Error code: " + toString((UINT64)GetLastError()));
  267. }
  268. DWORD filterFlags = 0;
  269. if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::FileName) != 0)
  270. filterFlags |= FILE_NOTIFY_CHANGE_FILE_NAME;
  271. if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::DirName) != 0)
  272. filterFlags |= FILE_NOTIFY_CHANGE_DIR_NAME;
  273. if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Attributes) != 0)
  274. filterFlags |= FILE_NOTIFY_CHANGE_ATTRIBUTES;
  275. if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Size) != 0)
  276. filterFlags |= FILE_NOTIFY_CHANGE_SIZE;
  277. if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::LastWrite) != 0)
  278. filterFlags |= FILE_NOTIFY_CHANGE_LAST_WRITE;
  279. if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::LastAccess) != 0)
  280. filterFlags |= FILE_NOTIFY_CHANGE_LAST_ACCESS;
  281. if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Creation) != 0)
  282. filterFlags |= FILE_NOTIFY_CHANGE_CREATION;
  283. if((((UINT32)changeFilter) & (UINT32)BansheeEngine::FolderChange::Security) != 0)
  284. filterFlags |= FILE_NOTIFY_CHANGE_SECURITY;
  285. mPimpl->mFoldersToWatch.push_back(bs_new<FolderWatchInfo>(folderPath, dirHandle, subdirectories, filterFlags));
  286. FolderWatchInfo* watchInfo = mPimpl->mFoldersToWatch.back();
  287. mPimpl->mCompPortHandle = CreateIoCompletionPort(dirHandle, mPimpl->mCompPortHandle, (ULONG_PTR)watchInfo, 0);
  288. if(mPimpl->mCompPortHandle == nullptr)
  289. {
  290. mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
  291. bs_delete(watchInfo);
  292. BS_EXCEPT(InternalErrorException, "Failed to open completition port for folder monitoring. Error code: " + toString((UINT64)GetLastError()));
  293. }
  294. if(mPimpl->mWorkerThread == nullptr)
  295. {
  296. BS_THREAD_CREATE(t, (std::bind(&FolderMonitor::workerThreadMain, this)));
  297. mPimpl->mWorkerThread = t;
  298. if(mPimpl->mWorkerThread == nullptr)
  299. {
  300. mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
  301. bs_delete(watchInfo);
  302. BS_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
  303. }
  304. }
  305. if(mPimpl->mWorkerThread != nullptr)
  306. {
  307. try
  308. {
  309. watchInfo->startMonitor(mPimpl->mCompPortHandle);
  310. }
  311. catch (Exception* e)
  312. {
  313. mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
  314. bs_delete(watchInfo);
  315. throw(e);
  316. }
  317. }
  318. else
  319. {
  320. mPimpl->mFoldersToWatch.erase(mPimpl->mFoldersToWatch.end() - 1);
  321. bs_delete(watchInfo);
  322. BS_EXCEPT(InternalErrorException, "Failed to create a new worker thread for folder monitoring");
  323. }
  324. }
  325. void FolderMonitor::stopMonitor(const Path& folderPath)
  326. {
  327. auto findIter = std::find_if(mPimpl->mFoldersToWatch.begin(), mPimpl->mFoldersToWatch.end(),
  328. [&](const FolderWatchInfo* x) { return x->mFolderToMonitor == folderPath; });
  329. if(findIter != mPimpl->mFoldersToWatch.end())
  330. {
  331. FolderWatchInfo* watchInfo = *findIter;
  332. watchInfo->stopMonitor(mPimpl->mCompPortHandle);
  333. bs_delete(watchInfo);
  334. mPimpl->mFoldersToWatch.erase(findIter);
  335. }
  336. if(mPimpl->mFoldersToWatch.size() == 0)
  337. stopMonitorAll();
  338. }
  339. void FolderMonitor::stopMonitorAll()
  340. {
  341. for(auto& watchInfo : mPimpl->mFoldersToWatch)
  342. {
  343. watchInfo->stopMonitor(mPimpl->mCompPortHandle);
  344. {
  345. // Note: Need this mutex to ensure worker thread is done with watchInfo.
  346. // Even though we wait for a condition variable from the worker thread in stopMonitor,
  347. // that doesn't mean the worker thread is done with the condition variable
  348. // (which is stored inside watchInfo)
  349. BS_LOCK_MUTEX(mPimpl->mMainMutex);
  350. bs_delete(watchInfo);
  351. }
  352. }
  353. mPimpl->mFoldersToWatch.clear();
  354. if(mPimpl->mWorkerThread != nullptr)
  355. {
  356. PostQueuedCompletionStatus(mPimpl->mCompPortHandle, 0, 0, nullptr);
  357. mPimpl->mWorkerThread->join();
  358. BS_THREAD_DESTROY(mPimpl->mWorkerThread);
  359. mPimpl->mWorkerThread = nullptr;
  360. }
  361. if(mPimpl->mCompPortHandle != nullptr)
  362. {
  363. CloseHandle(mPimpl->mCompPortHandle);
  364. mPimpl->mCompPortHandle = nullptr;
  365. }
  366. }
  367. void FolderMonitor::workerThreadMain()
  368. {
  369. FolderWatchInfo* watchInfo = nullptr;
  370. do
  371. {
  372. DWORD numBytes;
  373. LPOVERLAPPED overlapped;
  374. if(!GetQueuedCompletionStatus(mPimpl->mCompPortHandle, &numBytes, (PULONG_PTR) &watchInfo, &overlapped, INFINITE))
  375. {
  376. assert(false);
  377. // TODO: Folder handle was lost most likely. Not sure how to deal with that. Shutdown watch on this folder and cleanup?
  378. }
  379. if(watchInfo != nullptr)
  380. {
  381. MonitorState state;
  382. {
  383. BS_LOCK_MUTEX(watchInfo->mStatusMutex);
  384. state = watchInfo->mState;
  385. }
  386. switch(state)
  387. {
  388. case MonitorState::Starting:
  389. if(!ReadDirectoryChangesW(watchInfo->mDirHandle, watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE,
  390. watchInfo->mMonitorSubdirectories, watchInfo->mMonitorFlags, &watchInfo->mBufferSize, &watchInfo->mOverlapped, nullptr))
  391. {
  392. assert(false); // TODO - Possibly the buffer was too small?
  393. watchInfo->mReadError = GetLastError();
  394. }
  395. else
  396. {
  397. watchInfo->mReadError = ERROR_SUCCESS;
  398. {
  399. BS_LOCK_MUTEX(watchInfo->mStatusMutex);
  400. watchInfo->mState = MonitorState::Monitoring;
  401. }
  402. }
  403. BS_THREAD_NOTIFY_ONE(watchInfo->mStartStopEvent);
  404. break;
  405. case MonitorState::Monitoring:
  406. {
  407. FileNotifyInfo info(watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE);
  408. handleNotifications(info, *watchInfo);
  409. if(!ReadDirectoryChangesW(watchInfo->mDirHandle, watchInfo->mBuffer, FolderWatchInfo::READ_BUFFER_SIZE,
  410. watchInfo->mMonitorSubdirectories, watchInfo->mMonitorFlags, &watchInfo->mBufferSize, &watchInfo->mOverlapped, nullptr))
  411. {
  412. assert(false); // TODO: Failed during normal operation, possibly the buffer was too small. Shutdown watch on this folder and cleanup?
  413. watchInfo->mReadError = GetLastError();
  414. }
  415. else
  416. {
  417. watchInfo->mReadError = ERROR_SUCCESS;
  418. }
  419. }
  420. break;
  421. case MonitorState::Shutdown:
  422. if(watchInfo->mDirHandle != INVALID_HANDLE_VALUE)
  423. {
  424. CloseHandle(watchInfo->mDirHandle);
  425. watchInfo->mDirHandle = INVALID_HANDLE_VALUE;
  426. {
  427. BS_LOCK_MUTEX(watchInfo->mStatusMutex);
  428. watchInfo->mState = MonitorState::Shutdown2;
  429. }
  430. }
  431. else
  432. {
  433. {
  434. BS_LOCK_MUTEX(watchInfo->mStatusMutex);
  435. watchInfo->mState = MonitorState::Inactive;
  436. }
  437. {
  438. BS_LOCK_MUTEX(mPimpl->mMainMutex); // Ensures that we don't delete "watchInfo" before this thread is done with mStartStopEvent
  439. BS_THREAD_NOTIFY_ONE(watchInfo->mStartStopEvent);
  440. }
  441. }
  442. break;
  443. case MonitorState::Shutdown2:
  444. if(watchInfo->mDirHandle != INVALID_HANDLE_VALUE)
  445. {
  446. // Handle is still open? Try again.
  447. CloseHandle(watchInfo->mDirHandle);
  448. watchInfo->mDirHandle = INVALID_HANDLE_VALUE;
  449. }
  450. else
  451. {
  452. {
  453. BS_LOCK_MUTEX(watchInfo->mStatusMutex);
  454. watchInfo->mState = MonitorState::Inactive;
  455. }
  456. {
  457. BS_LOCK_MUTEX(mPimpl->mMainMutex); // Ensures that we don't delete "watchInfo" before this thread is done with mStartStopEvent
  458. BS_THREAD_NOTIFY_ONE(watchInfo->mStartStopEvent);
  459. }
  460. }
  461. break;
  462. }
  463. }
  464. } while (watchInfo != nullptr);
  465. }
  466. void FolderMonitor::handleNotifications(FileNotifyInfo& notifyInfo, FolderWatchInfo& watchInfo)
  467. {
  468. Vector<FileAction*> mActions;
  469. do
  470. {
  471. WString fullPath = notifyInfo.getFileNameWithPath(watchInfo.mFolderToMonitor);
  472. // Ignore notifications about hidden files
  473. if ((GetFileAttributesW(fullPath.c_str()) & FILE_ATTRIBUTE_HIDDEN) != 0)
  474. continue;
  475. switch(notifyInfo.getAction())
  476. {
  477. case FILE_ACTION_ADDED:
  478. mActions.push_back(FileAction::createAdded(fullPath));
  479. break;
  480. case FILE_ACTION_REMOVED:
  481. mActions.push_back(FileAction::createRemoved(fullPath));
  482. break;
  483. case FILE_ACTION_MODIFIED:
  484. mActions.push_back(FileAction::createModified(fullPath));
  485. break;
  486. case FILE_ACTION_RENAMED_OLD_NAME:
  487. watchInfo.mCachedOldFileName = fullPath;
  488. break;
  489. case FILE_ACTION_RENAMED_NEW_NAME:
  490. mActions.push_back(FileAction::createRenamed(watchInfo.mCachedOldFileName, fullPath));
  491. break;
  492. }
  493. } while(notifyInfo.getNext());
  494. {
  495. BS_LOCK_MUTEX(mPimpl->mMainMutex);
  496. for(auto& action : mActions)
  497. mPimpl->mFileActions.push(action);
  498. }
  499. }
  500. void FolderMonitor::_update()
  501. {
  502. {
  503. BS_LOCK_MUTEX(mPimpl->mMainMutex);
  504. while (!mPimpl->mFileActions.empty())
  505. {
  506. FileAction* action = mPimpl->mFileActions.front();
  507. mPimpl->mFileActions.pop();
  508. mPimpl->mActiveFileActions.push_back(action);
  509. }
  510. }
  511. for (auto iter = mPimpl->mActiveFileActions.begin(); iter != mPimpl->mActiveFileActions.end();)
  512. {
  513. FileAction* action = *iter;
  514. // Reported file actions might still be in progress (i.e. something might still be writing to those files).
  515. // Sadly there doesn't seem to be a way to properly determine when those files are done being written, so instead
  516. // we check for at least a couple of frames if the file's size hasn't changed before reporting a file action.
  517. // This takes care of most of the issues and avoids reporting partially written files in almost all cases.
  518. UINT64 size = FileSystem::getFileSize(action->newName);
  519. if (!action->checkForWriteStarted)
  520. {
  521. action->checkForWriteStarted = true;
  522. action->lastSize = size;
  523. ++iter;
  524. continue;
  525. }
  526. else
  527. {
  528. if (action->lastSize != size)
  529. {
  530. action->lastSize = size;
  531. ++iter;
  532. continue;
  533. }
  534. }
  535. switch (action->type)
  536. {
  537. case FileActionType::Added:
  538. if (!onAdded.empty())
  539. onAdded(Path(action->newName));
  540. break;
  541. case FileActionType::Removed:
  542. if (!onRemoved.empty())
  543. onRemoved(Path(action->newName));
  544. break;
  545. case FileActionType::Modified:
  546. if (!onModified.empty())
  547. onModified(Path(action->newName));
  548. break;
  549. case FileActionType::Renamed:
  550. if (!onRenamed.empty())
  551. onRenamed(Path(action->oldName), Path(action->newName));
  552. break;
  553. }
  554. mPimpl->mActiveFileActions.erase(iter++);
  555. FileAction::destroy(action);
  556. }
  557. }
  558. }